Reolink translate key (#140821)

* Add firmware exception translations

* Add test

* Much nicer syntax

* Check if translation key is present in string.json

* fix tests

* fix typo
This commit is contained in:
starkillerOG 2025-03-26 00:30:02 +01:00 committed by GitHub
parent 07bce8850f
commit e78a19ae3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 53 additions and 14 deletions

View File

@ -103,6 +103,12 @@
}, },
"config_entry_not_ready": { "config_entry_not_ready": {
"message": "Error while trying to set up {host}: {err}" "message": "Error while trying to set up {host}: {err}"
},
"update_already_running": {
"message": "Reolink firmware update already running, wait on completion before starting another"
},
"firmware_rate_limit": {
"message": "Reolink firmware update server reached hourly rate limit: updating can be tried again in 1 hour"
} }
}, },
"issues": { "issues": {

View File

@ -31,7 +31,7 @@ from .entity import (
ReolinkHostCoordinatorEntity, ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription, ReolinkHostEntityDescription,
) )
from .util import ReolinkConfigEntry, ReolinkData from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
RESUME_AFTER_INSTALL = 15 RESUME_AFTER_INSTALL = 15
@ -184,6 +184,7 @@ class ReolinkUpdateBaseEntity(
f"## Release notes\n\n{new_firmware.release_notes}" f"## Release notes\n\n{new_firmware.release_notes}"
) )
@raise_translated_error
async def async_install( async def async_install(
self, version: str | None, backup: bool, **kwargs: Any self, version: str | None, backup: bool, **kwargs: Any
) -> None: ) -> None:
@ -196,6 +197,8 @@ class ReolinkUpdateBaseEntity(
try: try:
await self._host.api.update_firmware(self._channel) await self._host.api.update_firmware(self._channel)
except ReolinkError as err: except ReolinkError as err:
if err.translation_key:
raise
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="firmware_install_error", translation_key="firmware_install_error",

View File

@ -27,6 +27,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.storage import Store from homeassistant.helpers.storage import Store
from homeassistant.helpers.translation import async_get_exception_message
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
@ -97,6 +98,16 @@ def get_device_uid_and_ch(
return (device_uid, ch, is_chime) return (device_uid, ch, is_chime)
def check_translation_key(err: ReolinkError) -> str | None:
"""Check if the translation key from the upstream library is present."""
if not err.translation_key:
return None
if async_get_exception_message(DOMAIN, err.translation_key) == err.translation_key:
# translation key not found in strings.json
return None
return err.translation_key
# Decorators # Decorators
def raise_translated_error[**P, R]( def raise_translated_error[**P, R](
func: Callable[P, Awaitable[R]], func: Callable[P, Awaitable[R]],
@ -110,73 +121,73 @@ def raise_translated_error[**P, R](
except InvalidParameterError as err: except InvalidParameterError as err:
raise ServiceValidationError( raise ServiceValidationError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="invalid_parameter", translation_key=check_translation_key(err) or "invalid_parameter",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except ApiError as err: except ApiError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="api_error", translation_key=check_translation_key(err) or "api_error",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except InvalidContentTypeError as err: except InvalidContentTypeError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="invalid_content_type", translation_key=check_translation_key(err) or "invalid_content_type",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except CredentialsInvalidError as err: except CredentialsInvalidError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="invalid_credentials", translation_key=check_translation_key(err) or "invalid_credentials",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except LoginError as err: except LoginError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="login_error", translation_key=check_translation_key(err) or "login_error",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except NoDataError as err: except NoDataError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="no_data", translation_key=check_translation_key(err) or "no_data",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except UnexpectedDataError as err: except UnexpectedDataError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="unexpected_data", translation_key=check_translation_key(err) or "unexpected_data",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except NotSupportedError as err: except NotSupportedError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="not_supported", translation_key=check_translation_key(err) or "not_supported",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except SubscriptionError as err: except SubscriptionError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="subscription_error", translation_key=check_translation_key(err) or "subscription_error",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except ReolinkConnectionError as err: except ReolinkConnectionError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="connection_error", translation_key=check_translation_key(err) or "connection_error",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except ReolinkTimeoutError as err: except ReolinkTimeoutError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="timeout", translation_key=check_translation_key(err) or "timeout",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err
except ReolinkError as err: except ReolinkError as err:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="unexpected", translation_key=check_translation_key(err) or "unexpected",
translation_placeholders={"err": str(err)}, translation_placeholders={"err": str(err)},
) from err ) from err

View File

@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from reolink_aio.exceptions import ReolinkError from reolink_aio.exceptions import ApiError, ReolinkError
from reolink_aio.software_version import NewSoftwareVersion from reolink_aio.software_version import NewSoftwareVersion
from homeassistant.components.reolink.update import POLL_AFTER_INSTALL, POLL_PROGRESS from homeassistant.components.reolink.update import POLL_AFTER_INSTALL, POLL_PROGRESS
@ -144,6 +144,17 @@ async def test_update_firm(
blocking=True, blocking=True,
) )
reolink_connect.update_firmware.side_effect = ApiError(
"Test error", translation_key="firmware_rate_limit"
)
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
UPDATE_DOMAIN,
SERVICE_INSTALL,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
# test _async_update_future # test _async_update_future
reolink_connect.camera_sw_version.return_value = "v3.3.0.226_23031644" reolink_connect.camera_sw_version.return_value = "v3.3.0.226_23031644"
reolink_connect.firmware_update_available.return_value = False reolink_connect.firmware_update_available.return_value = False

View File

@ -40,6 +40,14 @@ from tests.common import MockConfigEntry
ApiError("Test error"), ApiError("Test error"),
HomeAssistantError, HomeAssistantError,
), ),
(
ApiError("Test error", translation_key="firmware_rate_limit"),
HomeAssistantError,
),
(
ApiError("Test error", translation_key="not_in_strings.json"),
HomeAssistantError,
),
( (
CredentialsInvalidError("Test error"), CredentialsInvalidError("Test error"),
HomeAssistantError, HomeAssistantError,