mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Allow config entries to store a reason (#49581)
This commit is contained in:
parent
c753606a74
commit
265fdea83b
@ -390,4 +390,5 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict:
|
||||
"supports_options": supports_options,
|
||||
"supports_unload": entry.supports_unload,
|
||||
"disabled_by": entry.disabled_by,
|
||||
"reason": entry.reason,
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ class ConfigEntry:
|
||||
"disabled_by",
|
||||
"_setup_lock",
|
||||
"update_listeners",
|
||||
"reason",
|
||||
"_async_cancel_retry_setup",
|
||||
"_on_unload",
|
||||
)
|
||||
@ -202,6 +203,9 @@ class ConfigEntry:
|
||||
weakref.ReferenceType[UpdateListenerType] | weakref.WeakMethod
|
||||
] = []
|
||||
|
||||
# Reason why config entry is in a failed state
|
||||
self.reason: str | None = None
|
||||
|
||||
# Function to cancel a scheduled retry
|
||||
self._async_cancel_retry_setup: Callable[[], Any] | None = None
|
||||
|
||||
@ -236,6 +240,7 @@ class ConfigEntry:
|
||||
)
|
||||
if self.domain == integration.domain:
|
||||
self.state = ENTRY_STATE_SETUP_ERROR
|
||||
self.reason = "Import error"
|
||||
return
|
||||
|
||||
if self.domain == integration.domain:
|
||||
@ -249,13 +254,17 @@ class ConfigEntry:
|
||||
err,
|
||||
)
|
||||
self.state = ENTRY_STATE_SETUP_ERROR
|
||||
self.reason = "Import error"
|
||||
return
|
||||
|
||||
# Perform migration
|
||||
if not await self.async_migrate(hass):
|
||||
self.state = ENTRY_STATE_MIGRATION_ERROR
|
||||
self.reason = None
|
||||
return
|
||||
|
||||
error_reason = None
|
||||
|
||||
try:
|
||||
result = await component.async_setup_entry(hass, self) # type: ignore
|
||||
|
||||
@ -267,6 +276,7 @@ class ConfigEntry:
|
||||
except ConfigEntryAuthFailed as ex:
|
||||
message = str(ex)
|
||||
auth_base_message = "could not authenticate"
|
||||
error_reason = message or auth_base_message
|
||||
auth_message = (
|
||||
f"{auth_base_message}: {message}" if message else auth_base_message
|
||||
)
|
||||
@ -281,6 +291,7 @@ class ConfigEntry:
|
||||
result = False
|
||||
except ConfigEntryNotReady as ex:
|
||||
self.state = ENTRY_STATE_SETUP_RETRY
|
||||
self.reason = str(ex) or None
|
||||
wait_time = 2 ** min(tries, 4) * 5
|
||||
tries += 1
|
||||
message = str(ex)
|
||||
@ -329,8 +340,10 @@ class ConfigEntry:
|
||||
|
||||
if result:
|
||||
self.state = ENTRY_STATE_LOADED
|
||||
self.reason = None
|
||||
else:
|
||||
self.state = ENTRY_STATE_SETUP_ERROR
|
||||
self.reason = error_reason
|
||||
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Call when Home Assistant is stopping."""
|
||||
@ -352,6 +365,7 @@ class ConfigEntry:
|
||||
"""
|
||||
if self.source == SOURCE_IGNORE:
|
||||
self.state = ENTRY_STATE_NOT_LOADED
|
||||
self.reason = None
|
||||
return True
|
||||
|
||||
if integration is None:
|
||||
@ -363,6 +377,7 @@ class ConfigEntry:
|
||||
# that has been renamed without removing the config
|
||||
# entry.
|
||||
self.state = ENTRY_STATE_NOT_LOADED
|
||||
self.reason = None
|
||||
return True
|
||||
|
||||
component = integration.get_component()
|
||||
@ -375,6 +390,7 @@ class ConfigEntry:
|
||||
self.async_cancel_retry_setup()
|
||||
|
||||
self.state = ENTRY_STATE_NOT_LOADED
|
||||
self.reason = None
|
||||
return True
|
||||
|
||||
supports_unload = hasattr(component, "async_unload_entry")
|
||||
@ -382,6 +398,7 @@ class ConfigEntry:
|
||||
if not supports_unload:
|
||||
if integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_FAILED_UNLOAD
|
||||
self.reason = "Unload not supported"
|
||||
return False
|
||||
|
||||
try:
|
||||
@ -392,6 +409,7 @@ class ConfigEntry:
|
||||
# Only adjust state if we unloaded the component
|
||||
if result and integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_NOT_LOADED
|
||||
self.reason = None
|
||||
|
||||
self._async_process_on_unload()
|
||||
|
||||
@ -402,6 +420,7 @@ class ConfigEntry:
|
||||
)
|
||||
if integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_FAILED_UNLOAD
|
||||
self.reason = "Unknown error"
|
||||
return False
|
||||
|
||||
async def async_remove(self, hass: HomeAssistant) -> None:
|
||||
|
@ -734,6 +734,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
||||
connection_class=config_entries.CONN_CLASS_UNKNOWN,
|
||||
unique_id=None,
|
||||
disabled_by=None,
|
||||
reason=None,
|
||||
):
|
||||
"""Initialize a mock config entry."""
|
||||
kwargs = {
|
||||
@ -753,6 +754,8 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
||||
if state is not None:
|
||||
kwargs["state"] = state
|
||||
super().__init__(**kwargs)
|
||||
if reason is not None:
|
||||
self.reason = reason
|
||||
|
||||
def add_to_hass(self, hass):
|
||||
"""Test helper to add entry to hass."""
|
||||
|
@ -65,7 +65,8 @@ async def test_get_entries(hass, client):
|
||||
domain="comp2",
|
||||
title="Test 2",
|
||||
source="bla2",
|
||||
state=core_ce.ENTRY_STATE_LOADED,
|
||||
state=core_ce.ENTRY_STATE_SETUP_ERROR,
|
||||
reason="Unsupported API",
|
||||
connection_class=core_ce.CONN_CLASS_ASSUMED,
|
||||
).add_to_hass(hass)
|
||||
MockConfigEntry(
|
||||
@ -90,16 +91,18 @@ async def test_get_entries(hass, client):
|
||||
"supports_options": True,
|
||||
"supports_unload": True,
|
||||
"disabled_by": None,
|
||||
"reason": None,
|
||||
},
|
||||
{
|
||||
"domain": "comp2",
|
||||
"title": "Test 2",
|
||||
"source": "bla2",
|
||||
"state": "loaded",
|
||||
"state": "setup_error",
|
||||
"connection_class": "assumed",
|
||||
"supports_options": False,
|
||||
"supports_unload": False,
|
||||
"disabled_by": None,
|
||||
"reason": "Unsupported API",
|
||||
},
|
||||
{
|
||||
"domain": "comp3",
|
||||
@ -110,6 +113,7 @@ async def test_get_entries(hass, client):
|
||||
"supports_options": False,
|
||||
"supports_unload": False,
|
||||
"disabled_by": "user",
|
||||
"reason": None,
|
||||
},
|
||||
]
|
||||
|
||||
@ -330,6 +334,7 @@ async def test_create_account(hass, client):
|
||||
"supports_options": False,
|
||||
"supports_unload": False,
|
||||
"title": "Test Entry",
|
||||
"reason": None,
|
||||
},
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
@ -399,6 +404,7 @@ async def test_two_step_flow(hass, client):
|
||||
"supports_options": False,
|
||||
"supports_unload": False,
|
||||
"title": "user-title",
|
||||
"reason": None,
|
||||
},
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
|
@ -865,12 +865,14 @@ async def test_setup_raise_not_ready(hass, caplog):
|
||||
assert p_hass is hass
|
||||
assert p_wait_time == 5
|
||||
assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
|
||||
assert entry.reason == "The internet connection is offline"
|
||||
|
||||
mock_setup_entry.side_effect = None
|
||||
mock_setup_entry.return_value = True
|
||||
|
||||
await p_setup(None)
|
||||
assert entry.state == config_entries.ENTRY_STATE_LOADED
|
||||
assert entry.reason is None
|
||||
|
||||
|
||||
async def test_setup_raise_not_ready_from_exception(hass, caplog):
|
||||
@ -2555,6 +2557,7 @@ async def test_setup_raise_auth_failed(hass, caplog):
|
||||
assert "could not authenticate: The password is no longer valid" in caplog.text
|
||||
|
||||
assert entry.state == config_entries.ENTRY_STATE_SETUP_ERROR
|
||||
assert entry.reason == "The password is no longer valid"
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
||||
@ -2562,6 +2565,7 @@ async def test_setup_raise_auth_failed(hass, caplog):
|
||||
|
||||
caplog.clear()
|
||||
entry.state = config_entries.ENTRY_STATE_NOT_LOADED
|
||||
entry.reason = None
|
||||
|
||||
await entry.async_setup(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
Loading…
x
Reference in New Issue
Block a user