Handle single_instance_allowed centrally (#181143)

This commit is contained in:
Josef Zweck
2026-09-03 16:22:09 +02:00
committed by GitHub
parent 5a80328b1d
commit 0ee2592709
60 changed files with 137 additions and 136 deletions
@@ -1,8 +1,7 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Please confirm setting up the Deako integration"
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the [webhook service of Dialogflow]({dialogflow_url}) and update the webhook with following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"directory_does_not_exist": "The directory could not be reached. Please check your settings."
},
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Make sure to configure the 'split topic' data sources in DSMR Reader."
+1 -2
View File
@@ -1,8 +1,7 @@
{
"config": {
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Do you want to set up an Escea fireplace?"
@@ -3,7 +3,6 @@
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_data": "Received invalid data from Essent",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"user": {
"description": "Do you want to start the setup? The initial setup will take about 30-40 seconds."
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Do you want to set up Fjäråskupan?"
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"already_exists": "A camera with these URL settings already exists.",
"invalid_still_image": "URL did not return a valid still image",
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the webhook feature in Geofency and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the webhook feature in GPSLogger and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Do you want to set up Hisense AEH-W4A1?"
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the \"Make a web request\" action from the [IFTTT webhook applet]({applet_url}) and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
@@ -1,8 +1,7 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to webhooks in the Locative app and update webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to [webhooks in Mailgun]({mailgun_url}) and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
+1 -2
View File
@@ -9,8 +9,7 @@
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
+1 -2
View File
@@ -7,8 +7,7 @@
"addon_start_failed": "Failed to start the {addon} app.",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"bad_birth": "Invalid birth topic",
@@ -2,8 +2,7 @@
"config": {
"abort": {
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "The Thread border router is already configured",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
+1 -2
View File
@@ -2,8 +2,7 @@
"config": {
"abort": {
"mqtt_not_configured": "Home Assistant MQTT integration not configured.",
"mqtt_not_connected": "Home Assistant MQTT integration not connected to MQTT broker.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"mqtt_not_connected": "Home Assistant MQTT integration not connected to MQTT broker."
},
"step": {
"confirm_from_mqtt": {
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
@@ -12,7 +12,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from .const import (
CONF_DSN,
@@ -56,7 +56,10 @@ class SentryConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a user config flow."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
errors = {}
if user_input is not None:
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"bad_dsn": "Invalid DSN",
"unknown": "[%key:common::config_flow::error::unknown%]"
@@ -3,6 +3,7 @@
from typing import Any, override
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from .const import DEFAULT_NAME, DOMAIN
@@ -18,7 +19,10 @@ class SlimProtoConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# we have nothing to configure so simply create the entry
return self.async_create_entry(title=DEFAULT_NAME, data={})
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"user": {}
}
+1 -2
View File
@@ -2,8 +2,7 @@
"config": {
"abort": {
"not_ipv4_address": "No IPv4 address in SSDP discovery information",
"not_sonos_device": "Discovered device is not a Sonos device",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"not_sonos_device": "Discovered device is not a Sonos device"
},
"step": {
"confirm": {
@@ -9,7 +9,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from .const import (
CONF_SERVER_ID,
@@ -41,7 +41,10 @@ class SpeedTestFlowHandler(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
if user_input is None:
return self.async_show_form(step_id="user")
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"user": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
@@ -7,7 +7,6 @@
"invalid_config": "The YAML configuration is invalid and cannot be imported. Please check your configuration.yaml file.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
@@ -2,8 +2,7 @@
"config": {
"abort": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::already_configured_service%]"
},
"step": {
"user": {
"description": "Select **Submit** for initial setup. On the created config entry, select configure to add sensors for selected processes"
@@ -6,6 +6,7 @@ import voluptuous as vol
from homeassistant.components.mqtt import valid_subscribe_topic
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.helpers.service_info.mqtt import MqttServiceInfo
from .const import CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX, DOMAIN
@@ -26,7 +27,10 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a flow initialized by MQTT discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(DOMAIN)
@@ -50,7 +54,10 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
return await self.async_step_config()
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"invalid_discovery_topic": "Invalid discovery topic prefix."
},
@@ -10,6 +10,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_TOKEN
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from .const import DOMAIN
@@ -35,7 +36,10 @@ class TodoistConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
errors: dict[str, str] = {}
if user_input is not None:
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
},
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to webhooks in the Traccar Client and update the webhook with the following URL: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -2,7 +2,6 @@
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to [webhooks in Twilio]({twilio_url}) and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
@@ -5,6 +5,7 @@ import logging
from typing import Any, override
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
@@ -35,7 +36,10 @@ class ViCareFlowHandler(
) -> ConfigFlowResult:
"""Handle a flow initiated by the user."""
if self.source != SOURCE_REAUTH and self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
return await super().async_step_user(user_input)
@@ -81,6 +85,9 @@ class ViCareFlowHandler(
self._abort_if_unique_id_configured()
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
return await self.async_step_user()
@@ -7,7 +7,6 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"create_entry": {
+5 -2
View File
@@ -11,7 +11,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.helpers import config_validation as cv
from .const import CONF_SIP_PORT, CONF_SIP_USER, DOMAIN
@@ -28,7 +28,10 @@ class VoIPConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
if user_input is None:
return self.async_show_form(
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"user": {
"description": "Receive Voice over IP calls to interact with Assist."
@@ -9,7 +9,7 @@ from pyweatherflowudp.client import EVENT_DEVICE_DISCOVERED, WeatherFlowListener
from pyweatherflowudp.errors import AddressInUseError, EndpointError, ListenerError
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from .const import (
DOMAIN,
@@ -55,7 +55,10 @@ class WeatherFlowConfigFlow(ConfigFlow, domain=DOMAIN):
# will pick up all devices on the network and we don't want to
# create multiple entries.
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
found = False
errors = {}
try:
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"address_in_use": "Unable to open local UDP port 50222.",
"cannot_connect": "UDP discovery error.",
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "Do you want to set up Wemo?"
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
+9 -3
View File
@@ -31,7 +31,7 @@ from homeassistant.config_entries import (
OptionsFlow,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import (
@@ -828,7 +828,10 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
# Abort discovery if the device path is already configured
if path is not None and path in current_device_paths:
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# Without confirmation, discovery can automatically progress into parts of the
# config flow logic that interacts with hardware.
@@ -1032,7 +1035,10 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title="", data=data)
# This should never be reached
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
class ZhaOptionsFlowHandler(BaseZhaFlow, OptionsFlow):
@@ -8,7 +8,6 @@
"invalid_zeroconf_data": "The coordinator has invalid Zeroconf service info and cannot be identified by ZHA",
"not_zha_device": "This device is not a ZHA device",
"reconfigure_successful": "ZHA has successfully migrated from your old adapter to the new one. Give your Zigbee network a few minutes to stabilize.\n\nIf you no longer need the old adapter, you can now unplug it.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"usb_probe_failed": "Failed to probe the USB device",
"wrong_firmware_installed": "Your device is running the wrong firmware and cannot be used with ZHA until the correct firmware is installed. [A repair has been created]({repair_url}) with more information and instructions for how to fix this."
},
@@ -3,6 +3,7 @@
from typing import Any, override
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from .const import DEFAULT_NAME, DOMAIN
@@ -18,7 +19,10 @@ class ZodiacConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
if user_input is not None:
return self.async_create_entry(title=DEFAULT_NAME, data={})
@@ -1,8 +1,5 @@
{
"config": {
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"step": {
"user": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
+44 -11
View File
@@ -47,7 +47,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain, raise_on_progress=False)
@@ -83,7 +86,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
self.hass.config_entries.flow.async_abort(flow["flow_id"])
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
return self.async_create_entry(title=self._title, data={})
@@ -93,7 +99,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -105,7 +114,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by bluetooth discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -117,7 +129,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by dhcp discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -129,7 +144,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by Homekit discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -141,7 +159,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by mqtt discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -153,7 +174,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by Zeroconf discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -165,7 +189,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by Ssdp discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self.async_set_unique_id(self._domain)
@@ -176,7 +203,10 @@ class DiscoveryFlowHandler[_R: Awaitable[bool] | bool](config_entries.ConfigFlow
) -> config_entries.ConfigFlowResult:
"""Handle a flow initialized by import."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# Cancel other flows.
in_progress = self._async_in_progress()
@@ -230,7 +260,10 @@ class WebhookFlowHandler(config_entries.ConfigFlow):
and self._async_current_entries()
and self.source != config_entries.SOURCE_RECONFIGURE
):
return self.async_abort(reason="single_instance_allowed")
return self.async_abort(
reason="single_instance_allowed",
translation_domain=HOMEASSISTANT_DOMAIN,
)
if user_input is None:
return self.async_show_form(
+22
View File
@@ -77,6 +77,7 @@ async def test_single_entry_allowed(
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
async def test_user_no_devices_found(
@@ -152,6 +153,7 @@ async def test_user_has_confirmation_async_discovery_flow(
[
config_entries.SOURCE_BLUETOOTH,
config_entries.SOURCE_DISCOVERY,
config_entries.SOURCE_HOMEKIT,
config_entries.SOURCE_MQTT,
config_entries.SOURCE_SSDP,
config_entries.SOURCE_ZEROCONF,
@@ -171,6 +173,23 @@ async def test_discovery_single_instance(
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
async def test_discovery_confirm_single_instance(
hass: HomeAssistant, discovery_flow_conf: dict[str, bool]
) -> None:
"""Test confirming a discovery aborts once an entry exists."""
flow = config_entries.HANDLERS["test"]()
flow.hass = hass
flow.context = {"source": config_entries.SOURCE_DISCOVERY}
MockConfigEntry(domain="test").add_to_hass(hass)
result = await flow.async_step_confirm({})
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
@pytest.mark.parametrize(
@@ -322,6 +341,8 @@ async def test_import_single_instance(
result = await flow.async_step_import(None)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
async def test_ignored_discoveries(
@@ -370,6 +391,7 @@ async def test_webhook_single_entry_allowed(
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
async def test_webhook_multiple_entries_allowed(