diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 42194641f7f..890db26810a 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -243,7 +243,14 @@ class OperationNotAllowed(ConfigError): UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Coroutine[Any, Any, None]] -FROZEN_CONFIG_ENTRY_ATTRS = {"entry_id", "domain", "state", "reason"} +FROZEN_CONFIG_ENTRY_ATTRS = { + "entry_id", + "domain", + "state", + "reason", + "error_reason_translation_key", + "error_reason_translation_placeholders", +} UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = { "unique_id", "title", @@ -274,6 +281,8 @@ class ConfigEntry: unique_id: str | None state: ConfigEntryState reason: str | None + error_reason_translation_key: str | None + error_reason_translation_placeholders: dict[str, Any] | None pref_disable_new_entities: bool pref_disable_polling: bool version: int @@ -369,6 +378,8 @@ class ConfigEntry: # Reason why config entry is in a failed state _setter(self, "reason", None) + _setter(self, "error_reason_translation_key", None) + _setter(self, "error_reason_translation_placeholders", None) # Function to cancel a scheduled retry self._async_cancel_retry_setup: Callable[[], Any] | None = None @@ -472,6 +483,8 @@ class ConfigEntry: "pref_disable_polling": self.pref_disable_polling, "disabled_by": self.disabled_by, "reason": self.reason, + "error_reason_translation_key": self.error_reason_translation_key, + "error_reason_translation_placeholders": self.error_reason_translation_placeholders, } return json_fragment(json_bytes(json_repr)) @@ -543,6 +556,8 @@ class ConfigEntry: setup_phase = SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP error_reason = None + error_reason_translation_key = None + error_reason_translation_placeholders = None try: with async_start_setup( @@ -557,6 +572,8 @@ class ConfigEntry: result = False except ConfigEntryError as exc: error_reason = str(exc) or "Unknown fatal config entry error" + error_reason_translation_key = exc.translation_key + error_reason_translation_placeholders = exc.translation_placeholders _LOGGER.exception( "Error setting up entry %s for %s: %s", self.title, @@ -569,6 +586,8 @@ class ConfigEntry: message = str(exc) auth_base_message = "could not authenticate" error_reason = message or auth_base_message + error_reason_translation_key = exc.translation_key + error_reason_translation_placeholders = exc.translation_placeholders auth_message = ( f"{auth_base_message}: {message}" if message else auth_base_message ) @@ -583,7 +602,15 @@ class ConfigEntry: result = False except ConfigEntryNotReady as exc: message = str(exc) - self._async_set_state(hass, ConfigEntryState.SETUP_RETRY, message or None) + error_reason_translation_key = exc.translation_key + error_reason_translation_placeholders = exc.translation_placeholders + self._async_set_state( + hass, + ConfigEntryState.SETUP_RETRY, + message or None, + error_reason_translation_key, + error_reason_translation_placeholders, + ) wait_time = 2 ** min(self._tries, 4) * 5 + ( randint(RANDOM_MICROSECOND_MIN, RANDOM_MICROSECOND_MAX) / 1000000 ) @@ -644,7 +671,13 @@ class ConfigEntry: if result: self._async_set_state(hass, ConfigEntryState.LOADED, None) else: - self._async_set_state(hass, ConfigEntryState.SETUP_ERROR, error_reason) + self._async_set_state( + hass, + ConfigEntryState.SETUP_ERROR, + error_reason, + error_reason_translation_key, + error_reason_translation_placeholders, + ) @callback def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None: @@ -771,7 +804,12 @@ class ConfigEntry: @callback def _async_set_state( - self, hass: HomeAssistant, state: ConfigEntryState, reason: str | None + self, + hass: HomeAssistant, + state: ConfigEntryState, + reason: str | None, + error_reason_translation_key: str | None = None, + error_reason_translation_placeholders: dict[str, str] | None = None, ) -> None: """Set the state of the config entry.""" if state not in NO_RESET_TRIES_STATES: @@ -779,6 +817,12 @@ class ConfigEntry: _setter = object.__setattr__ _setter(self, "state", state) _setter(self, "reason", reason) + _setter(self, "error_reason_translation_key", error_reason_translation_key) + _setter( + self, + "error_reason_translation_placeholders", + error_reason_translation_placeholders, + ) self.clear_cache() async_dispatcher_send( hass, SIGNAL_CONFIG_ENTRY_CHANGED, ConfigEntryChange.UPDATED, self diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index b4ef32b864c..e2929a0f1d5 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -131,6 +131,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "pref_disable_polling": False, "disabled_by": None, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, { "domain": "comp2", @@ -145,6 +147,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "pref_disable_polling": False, "disabled_by": None, "reason": "Unsupported API", + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, { "domain": "comp3", @@ -159,6 +163,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "pref_disable_polling": False, "disabled_by": core_ce.ConfigEntryDisabler.USER, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, { "domain": "comp4", @@ -173,6 +179,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "pref_disable_polling": False, "disabled_by": None, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, { "domain": "comp5", @@ -187,6 +195,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "pref_disable_polling": False, "disabled_by": None, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, ] @@ -536,6 +546,8 @@ async def test_create_account( "pref_disable_polling": False, "title": "Test Entry", "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, "description": None, "description_placeholders": None, @@ -615,6 +627,8 @@ async def test_two_step_flow( "pref_disable_polling": False, "title": "user-title", "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, "description": None, "description_placeholders": None, @@ -1058,6 +1072,8 @@ async def test_get_single( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "user", "state": "loaded", "supports_reconfigure": False, @@ -1393,6 +1409,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1408,6 +1426,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla2", "state": "setup_error", "supports_reconfigure": False, @@ -1423,6 +1443,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -1438,6 +1460,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla4", "state": "not_loaded", "supports_reconfigure": False, @@ -1453,6 +1477,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla5", "state": "not_loaded", "supports_reconfigure": False, @@ -1479,6 +1505,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1504,6 +1532,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla4", "state": "not_loaded", "supports_reconfigure": False, @@ -1519,6 +1549,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla5", "state": "not_loaded", "supports_reconfigure": False, @@ -1544,6 +1576,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1559,6 +1593,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -1590,6 +1626,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1605,6 +1643,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla2", "state": "setup_error", "supports_reconfigure": False, @@ -1620,6 +1660,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -1635,6 +1677,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla4", "state": "not_loaded", "supports_reconfigure": False, @@ -1650,6 +1694,8 @@ async def test_get_matching_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla5", "state": "not_loaded", "supports_reconfigure": False, @@ -1749,6 +1795,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1767,6 +1815,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla2", "state": "setup_error", "supports_reconfigure": False, @@ -1785,6 +1835,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -1807,6 +1859,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1830,6 +1884,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1853,6 +1909,8 @@ async def test_subscribe_entries_ws( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1935,6 +1993,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1953,6 +2013,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -1977,6 +2039,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -1999,6 +2063,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla3", "state": "not_loaded", "supports_reconfigure": False, @@ -2023,6 +2089,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -2046,6 +2114,8 @@ async def test_subscribe_entries_ws_filtered( "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, "source": "bla", "state": "not_loaded", "supports_reconfigure": False, @@ -2225,6 +2295,8 @@ async def test_supports_reconfigure( "pref_disable_polling": False, "title": "Test Entry", "reason": None, + "error_reason_translation_key": None, + "error_reason_translation_placeholders": None, }, "description": None, "description_placeholders": None, diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 7d564f1cf12..6af5f2cde3f 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -821,6 +821,8 @@ async def test_as_dict(snapshot: SnapshotAssertion) -> None: "_setup_lock", "update_listeners", "reason", + "error_reason_translation_key", + "error_reason_translation_placeholders", "_async_cancel_retry_setup", "_on_unload", "reload_lock",