Allow config entries to store a reason (#49581)

This commit is contained in:
Paulus Schoutsen 2021-04-23 00:23:43 -07:00 committed by GitHub
parent c753606a74
commit 265fdea83b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 2 deletions

View File

@ -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,
}

View File

@ -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:

View File

@ -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."""

View File

@ -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,

View File

@ -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()