Centrally handle reconfigure successful for subentries (#181225)

This commit is contained in:
Josef Zweck
2026-09-09 21:39:55 +02:00
committed by GitHub
parent c0dbcdf8e9
commit 1437491e0a
18 changed files with 97 additions and 46 deletions
@@ -33,8 +33,7 @@
"config_subentries": {
"ai_task_data": {
"abort": {
"entry_not_loaded": "[%key:component::anthropic::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::anthropic::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "AI task",
"error": {
@@ -100,8 +99,7 @@
},
"conversation": {
"abort": {
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "Cannot add things while the configuration is disabled."
},
"entry_type": "Conversation agent",
"error": {
@@ -94,9 +94,6 @@
},
"config_subentries": {
"observation": {
"abort": {
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"entry_type": "Observation",
"error": {
"above_below": "[%key:component::bayesian::config::error::above_below%]",
@@ -362,6 +362,7 @@ class FishAudioSubentryFlowHandler(ConfigSubentryFlow):
self._get_reconfigure_subentry(),
data=self.config_data,
unique_id=unique_id,
reason="reconfigure_successful",
)
return self.async_show_form(
@@ -17,8 +17,7 @@
"plane": {
"abort": {
"api_key_required": "An API key is required to add more than one plane. You can configure it in the integration options.",
"max_planes": "You can add a maximum of 4 planes.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"max_planes": "You can add a maximum of 4 planes."
},
"entry_type": "Plane",
"initiate_flow": {
@@ -24,8 +24,7 @@
"config_subentries": {
"ai_task_data": {
"abort": {
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "AI task",
"initiate_flow": {
@@ -54,8 +53,7 @@
},
"conversation": {
"abort": {
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "Cannot add things while the configuration is disabled."
},
"entry_type": "Conversation agent",
"error": {
@@ -96,8 +94,7 @@
},
"stt": {
"abort": {
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "Speech-to-text",
"initiate_flow": {
@@ -130,8 +127,7 @@
},
"tts": {
"abort": {
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "Text-to-speech",
"initiate_flow": {
@@ -38,8 +38,7 @@
"location": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "Cannot add things while the configuration is disabled."
},
"entry_type": "Location",
"error": {
@@ -27,7 +27,6 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before reconfiguring.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"entry_type": "Conversation agent",
@@ -104,9 +104,6 @@
},
"config_subentries": {
"device": {
"abort": {
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"create_entry": {
"default": "MQTT device with {platform} entity \"{entity}\" was set up successfully.\n\nNote that you can reconfigure the MQTT device at any time, e.g. to add more entities."
},
+1 -2
View File
@@ -83,8 +83,7 @@
"config_subentries": {
"topic": {
"abort": {
"already_configured": "Topic is already configured",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"already_configured": "Topic is already configured"
},
"entry_type": "[%key:component::ntfy::common::topic%]",
"error": {
+2 -4
View File
@@ -30,8 +30,7 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"download_failed": "[%key:component::ollama::config_subentries::conversation::abort::download_failed%]",
"entry_not_loaded": "[%key:component::ollama::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::ollama::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "AI task",
"initiate_flow": {
@@ -68,8 +67,7 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"download_failed": "Model downloading failed",
"entry_not_loaded": "Failed to add agent. The configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "Failed to add agent. The configuration is disabled."
},
"entry_type": "Conversation agent",
"initiate_flow": {
@@ -23,7 +23,6 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before reconfiguring.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"entry_type": "AI task",
@@ -44,7 +43,6 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"entry_not_loaded": "[%key:component::open_router::config_subentries::ai_task_data::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"entry_type": "Conversation agent",
@@ -32,8 +32,7 @@
"config_subentries": {
"ai_task_data": {
"abort": {
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "AI task",
"error": {
@@ -96,8 +95,7 @@
},
"conversation": {
"abort": {
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "Cannot add things while the configuration is disabled."
},
"entry_type": "Conversation agent",
"error": {
@@ -165,8 +163,7 @@
},
"stt": {
"abort": {
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "Speech-to-text",
"initiate_flow": {
@@ -189,8 +186,7 @@
},
"tts": {
"abort": {
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
},
"entry_type": "Text-to-speech",
"initiate_flow": {
@@ -42,7 +42,6 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before configuring.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"entry_type": "Conversation agent",
@@ -38,8 +38,7 @@
"list": {
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"entry_not_loaded": "The Remember The Milk account must be loaded before adding or renaming a list.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"entry_not_loaded": "The Remember The Milk account must be loaded before adding or renaming a list."
},
"entry_type": "List",
"error": {
+1 -2
View File
@@ -86,8 +86,7 @@
"config_subentries": {
"recipient": {
"abort": {
"already_configured": "Recipient is already configured",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"already_configured": "Recipient is already configured"
},
"entry_type": "Recipient",
"initiate_flow": {
+16 -2
View File
@@ -3835,6 +3835,7 @@ class ConfigSubentryFlow(
title: str | UndefinedType = UNDEFINED,
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
data_updates: Mapping[str, Any] | UndefinedType = UNDEFINED,
reason: str | UndefinedType = UNDEFINED,
) -> SubentryFlowResult:
"""Update config subentry and finish subentry flow.
@@ -3843,6 +3844,8 @@ class ConfigSubentryFlow(
keys are overridden
:param title: replace the title of the subentry
:param unique_id: replace the unique_id of the subentry
:param reason: set the reason for the abort, defaults to
`reconfigure_successful`. A custom reason requires a matching strings.json entry
"""
self._async_update(
entry=entry,
@@ -3852,7 +3855,11 @@ class ConfigSubentryFlow(
data=data,
data_updates=data_updates,
)
return self.async_abort(reason="reconfigure_successful")
translation_domain: str | None = None
if reason is UNDEFINED:
reason = "reconfigure_successful"
translation_domain = HOMEASSISTANT_DOMAIN
return self.async_abort(reason=reason, translation_domain=translation_domain)
@callback
def async_update_reload_and_abort(
@@ -3864,6 +3871,7 @@ class ConfigSubentryFlow(
title: str | UndefinedType = UNDEFINED,
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
data_updates: Mapping[str, Any] | UndefinedType = UNDEFINED,
reason: str | UndefinedType = UNDEFINED,
reload_even_if_entry_is_unchanged: bool = True,
) -> SubentryFlowResult:
"""Update config subentry, reload config entry and finish subentry flow.
@@ -3873,6 +3881,8 @@ class ConfigSubentryFlow(
keys are overridden
:param title: replace the title of the subentry
:param unique_id: replace the unique_id of the subentry
:param reason: set the reason for the abort, defaults to
`reconfigure_successful`. A custom reason requires a matching strings.json entry
:param reload_even_if_entry_is_unchanged: set this to `False` if the entry
should not be reloaded if it is unchanged
"""
@@ -3888,7 +3898,11 @@ class ConfigSubentryFlow(
if entry.update_listeners:
raise ValueError("Cannot update and reload entry with update listeners")
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
translation_domain: str | None = None
if reason is UNDEFINED:
reason = "reconfigure_successful"
translation_domain = HOMEASSISTANT_DOMAIN
return self.async_abort(reason=reason, translation_domain=translation_domain)
@property
def _entry_id(self) -> str:
@@ -1729,6 +1729,7 @@ async def test_subentry_reconfigure_flow(hass: HomeAssistant, client) -> None:
assert data == {
"handler": ["test1", "test"],
"reason": "reconfigure_successful",
"translation_domain": "homeassistant",
"type": "abort",
"description_placeholders": None,
}
+62
View File
@@ -7746,6 +7746,62 @@ async def test_update_subentry_and_abort(
else:
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
@pytest.mark.parametrize(
"update_and_abort",
["async_update_and_abort", "async_update_reload_and_abort"],
)
async def test_update_subentry_custom_reason_stays_local(
hass: HomeAssistant, update_and_abort: str
) -> None:
"""Test a caller supplied reason resolves against the integration itself."""
subentry_id = "blabla"
entry = MockConfigEntry(
domain="comp",
unique_id="entry_unique_id",
title="entry_title",
data={},
subentries_data=[
config_entries.ConfigSubentryData(
data={"vendor": "data"},
subentry_id=subentry_id,
subentry_type="test",
unique_id="1234",
title="Test",
)
],
)
entry.add_to_hass(hass)
mock_integration(hass, MockModule("comp"))
mock_platform(hass, "comp.config_flow", None)
class TestFlow(config_entries.ConfigFlow):
class SubentryFlowHandler(config_entries.ConfigSubentryFlow):
async def async_step_reconfigure(self, user_input=None):
return getattr(self, update_and_abort)(
self._get_entry(),
self._get_reconfigure_subentry(),
reason="custom_reason",
)
@classmethod
@callback
def async_get_supported_subentry_types(
cls, config_entry: config_entries.ConfigEntry
) -> dict[str, type[config_entries.ConfigSubentryFlow]]:
return {"test": TestFlow.SubentryFlowHandler}
with mock_config_flow("comp", TestFlow):
result = await entry.start_subentry_reconfigure_flow(hass, subentry_id)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "custom_reason"
assert "translation_domain" not in result
@pytest.mark.parametrize(
@@ -7775,6 +7831,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),
@@ -7793,6 +7850,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),
@@ -7811,6 +7869,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),
@@ -7825,6 +7884,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),
@@ -7841,6 +7901,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),
@@ -7855,6 +7916,7 @@ async def test_update_subentry_and_abort(
{
"type": FlowResultType.ABORT,
"reason": "reconfigure_successful",
"translation_domain": HOMEASSISTANT_DOMAIN,
"description_placeholders": None,
},
),