Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/config/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
19 changes: 19 additions & 0 deletions homeassistant/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class ConfigEntry:
"disabled_by",
"_setup_lock",
"update_listeners",
"reason",
"_async_cancel_retry_setup",
"_on_unload",
)
Expand Down Expand Up @@ -202,6 +203,9 @@ def __init__(
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

Expand Down Expand Up @@ -236,6 +240,7 @@ async def async_setup(
)
if self.domain == integration.domain:
self.state = ENTRY_STATE_SETUP_ERROR
self.reason = "Import error"
return

if self.domain == integration.domain:
Expand All @@ -249,13 +254,17 @@ async def async_setup(
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

Comment thread
bdraco marked this conversation as resolved.
try:
result = await component.async_setup_entry(hass, self) # type: ignore

Expand All @@ -267,6 +276,7 @@ async def async_setup(
except ConfigEntryAuthFailed as ex:
message = str(ex)
auth_base_message = "could not authenticate"
error_reason = message or auth_base_message
Comment thread
bdraco marked this conversation as resolved.
auth_message = (
f"{auth_base_message}: {message}" if message else auth_base_message
)
Expand All @@ -281,6 +291,7 @@ async def async_setup(
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)
Expand Down Expand Up @@ -329,8 +340,10 @@ async def setup_again(*_: Any) -> None:

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."""
Expand All @@ -352,6 +365,7 @@ async def async_unload(
"""
if self.source == SOURCE_IGNORE:
self.state = ENTRY_STATE_NOT_LOADED
self.reason = None
return True

if integration is None:
Expand All @@ -363,6 +377,7 @@ async def async_unload(
# that has been renamed without removing the config
# entry.
self.state = ENTRY_STATE_NOT_LOADED
self.reason = None
return True

component = integration.get_component()
Expand All @@ -375,13 +390,15 @@ async def async_unload(
self.async_cancel_retry_setup()

self.state = ENTRY_STATE_NOT_LOADED
self.reason = None
return True

supports_unload = hasattr(component, "async_unload_entry")

if not supports_unload:
if integration.domain == self.domain:
self.state = ENTRY_STATE_FAILED_UNLOAD
self.reason = "Unload not supported"
return False

try:
Expand All @@ -392,6 +409,7 @@ async def async_unload(
# 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()

Expand All @@ -402,6 +420,7 @@ async def async_unload(
)
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:
Expand Down
3 changes: 3 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ def __init__(
connection_class=config_entries.CONN_CLASS_UNKNOWN,
unique_id=None,
disabled_by=None,
reason=None,
):
"""Initialize a mock config entry."""
kwargs = {
Expand All @@ -753,6 +754,8 @@ def __init__(
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."""
Expand Down
10 changes: 8 additions & 2 deletions tests/components/config/test_config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def async_get_options_flow(config, options):
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(
Expand All @@ -90,16 +91,18 @@ def async_get_options_flow(config, options):
"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",
Expand All @@ -110,6 +113,7 @@ def async_get_options_flow(config, options):
"supports_options": False,
"supports_unload": False,
"disabled_by": "user",
"reason": None,
},
]

Expand Down Expand Up @@ -330,6 +334,7 @@ async def async_step_user(self, user_input=None):
"supports_options": False,
"supports_unload": False,
"title": "Test Entry",
"reason": None,
},
"description": None,
"description_placeholders": None,
Expand Down Expand Up @@ -399,6 +404,7 @@ async def async_step_account(self, user_input=None):
"supports_options": False,
"supports_unload": False,
"title": "user-title",
"reason": None,
},
"description": None,
"description_placeholders": None,
Expand Down
4 changes: 4 additions & 0 deletions tests/test_config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -2555,13 +2557,15 @@ 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
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH

caplog.clear()
entry.state = config_entries.ENTRY_STATE_NOT_LOADED
entry.reason = None

await entry.async_setup(hass)
await hass.async_block_till_done()
Expand Down