Reduce boilerplate to abort for matching config entries (#50186)

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
J. Nick Koston 2021-05-11 15:00:12 -05:00 committed by GitHub
parent d6a202bd74
commit 34c84a6bbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 183 additions and 350 deletions

View file

@ -65,13 +65,9 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
if user_input is None:
return await self._show_setup_form(user_input)
entries = self._async_current_entries()
for entry in entries:
if (
entry.data[CONF_HOST] == user_input[CONF_HOST]
and entry.data[CONF_PORT] == user_input[CONF_PORT]
):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match(
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)
errors = {}

View file

@ -50,8 +50,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
"""Handle external yaml configuration."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
config = self.hass.data.get(DATA_AMBICLIMATE_IMPL, {})
@ -63,8 +62,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_auth(self, user_input=None):
"""Handle a flow start."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
errors = {}
@ -85,8 +83,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_code(self, code=None):
"""Received code for authentication."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
token_info = await self._get_token_info(code)

View file

@ -298,11 +298,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, import_info):
"""Import a device."""
if any(
import_info[CONF_HOST] == entry.data[CONF_HOST]
for entry in self._async_current_entries()
):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: import_info[CONF_HOST]})
return await self.async_step_user(import_info)
async def async_step_reauth(self, data):

View file

@ -199,9 +199,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"unique_id's will not be available",
self.host,
)
for entry in self._async_current_entries():
if entry.data[CONF_HOST] == self.host:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: self.host})
return self.async_create_entry(
title=receiver.name,

View file

@ -73,8 +73,7 @@ class DuneHDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle configuration by yaml file."""
self.host = user_input[CONF_HOST]
if self.host_already_configured(self.host):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: self.host})
try:
await self.init_device(self.host)

View file

@ -26,12 +26,8 @@ class EmulatedRokuFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
name = user_input[CONF_NAME]
if name in configured_servers(self.hass):
return self.async_abort(reason="already_configured")
return self.async_create_entry(title=name, data=user_input)
self._async_abort_entries_match({CONF_NAME: user_input[CONF_NAME]})
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)
servers_num = len(configured_servers(self.hass))

View file

@ -133,9 +133,7 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""
if user_input is not None:
# check for any entries with same host, abort if found
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == user_input[CONF_HOST]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
validate_result = await self.validate_input(user_input)
if validate_result[0] == "ok": # success
_LOGGER.debug("Connected successfully. Creating entry")

View file

@ -47,13 +47,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
for entry in self.hass.config_entries.async_entries(DOMAIN):
if (
entry.data[CONF_HOST] == data[CONF_HOST]
and entry.data[CONF_PORT] == data[CONF_PORT]
):
raise AbortFlow("already_configured")
self._async_abort_entries_match(
{CONF_HOST: data[CONF_HOST], CONF_PORT: data[CONF_PORT]}
)
camera = FoscamCamera(
data[CONF_HOST],

View file

@ -92,10 +92,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.data[CONF_HOST] == user_input[CONF_HOST]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
self._host = user_input[CONF_HOST]
self._name = user_input[CONF_HOST]

View file

@ -28,8 +28,7 @@ class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
host = user_input[CONF_HOST]
name = user_input[CONF_NAME]
if await self._async_endpoint_existed(host):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: host})
try:
await self._async_try_connect(host)
@ -64,12 +63,6 @@ class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def _async_endpoint_existed(self, endpoint):
for entry in self._async_current_entries():
if endpoint == entry.data.get(CONF_HOST):
return True
return False
async def _async_try_connect(self, host):
session = async_get_clientsession(self.hass)
api = Yeti(host, self.hass.loop, session)

View file

@ -35,9 +35,7 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
ip_address = discovery_info["host"]
for entry in self._async_current_entries():
if entry.data.get(CONF_IP_ADDRESS) == ip_address:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})
self._ip_address = ip_address
self._device_type = DEVICE_TYPE_ISMARTGATE

View file

@ -6,7 +6,6 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import callback
from .const import (
CONF_2FA,
@ -22,15 +21,6 @@ from .hangups_utils import (
)
@callback
def configured_hangouts(hass):
"""Return the configures Google Hangouts Account."""
entries = hass.config_entries.async_entries(HANGOUTS_DOMAIN)
if entries:
return entries[0]
return None
@config_entries.HANDLERS.register(HANGOUTS_DOMAIN)
class HangoutsFlowHandler(config_entries.ConfigFlow):
"""Config flow Google Hangouts."""
@ -46,8 +36,7 @@ class HangoutsFlowHandler(config_entries.ConfigFlow):
"""Handle a flow start."""
errors = {}
if configured_hangouts(self.hass) is not None:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
if user_input is not None:
user_email = user_input[CONF_EMAIL]

View file

@ -85,8 +85,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
friendly_name = discovery_info[ssdp.ATTR_UPNP_FRIENDLY_NAME]
if self._host_already_configured(parsed_url.hostname):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: parsed_url.hostname})
self.context["title_placeholders"] = {"name": friendly_name}
@ -147,16 +146,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=validated[CONF_NAME], data=data)
def _host_already_configured(self, host):
"""See if we already have a harmony entry matching the host."""
for entry in self._async_current_entries():
if CONF_HOST not in entry.data:
continue
if entry.data[CONF_HOST] == host:
return True
return False
def _options_from_user_input(user_input):
options = {}

View file

@ -125,12 +125,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
)
if any(
user_input["host"] == entry.data.get("host")
for entry in self._async_current_entries()
):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({"host": user_input["host"]})
self.bridge = self._async_get_bridge(user_input[CONF_HOST])
return await self.async_step_link()
@ -233,11 +228,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
This flow is also triggered by `async_step_discovery`.
"""
# Check if host exists, abort if so.
if any(
import_info["host"] == entry.data.get("host")
for entry in self._async_current_entries()
):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({"host": import_info["host"]})
self.bridge = self._async_get_bridge(import_info["host"])
return await self.async_step_link()

View file

@ -7,7 +7,7 @@ from aiopvapi.helpers.aiorequest import AioRequest
import async_timeout
import voluptuous as vol
from homeassistant import config_entries, core, data_entry_flow, exceptions
from homeassistant import config_entries, core, exceptions
from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -73,8 +73,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
)
async def _async_validate_or_error(self, host):
if self._host_already_configured(host):
raise data_entry_flow.AbortFlow("already_configured")
self._async_abort_entries_match({CONF_HOST: host})
try:
info = await validate_input(self.hass, host)
@ -118,8 +117,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if progress.get("context", {}).get(CONF_HOST) == self.discovered_ip:
return self.async_abort(reason="already_in_progress")
if self._host_already_configured(self.discovered_ip):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: self.discovered_ip})
info, error = await self._async_validate_or_error(self.discovered_ip)
if error:
@ -148,15 +146,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="link", description_placeholders=self.powerview_config
)
def _host_already_configured(self, host):
"""See if we already have a hub with the host address configured."""
existing_hosts = {
entry.data.get(CONF_HOST)
for entry in self._async_current_entries()
if CONF_HOST in entry.data
}
return host in existing_hosts
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""

View file

@ -47,9 +47,7 @@ class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.data[CONF_HOST] == user_input[CONF_HOST]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
_client = Client(
TelnetConnection(

View file

@ -8,7 +8,7 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_BASE, CONF_HOST, CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
@ -23,14 +23,6 @@ DATA_SCHEMA = vol.Schema(
)
@callback
def configured_instances(hass):
"""Return a set of configured Kostal Plenticore HOSTS."""
return {
entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN)
}
async def test_connection(hass: HomeAssistant, data) -> str:
"""Test the connection to the inverter.
@ -56,8 +48,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
hostname = None
if user_input is not None:
if user_input[CONF_HOST] in configured_instances(self.hass):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
try:
hostname = await test_connection(self.hass, user_input)
except PlenticoreAuthenticationException as ex:

View file

@ -27,9 +27,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
for entry in self._async_current_entries():
if entry.data[CONF_USERNAME] == user_input[CONF_USERNAME]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]})
hub = LitterRobotHub(self.hass, user_input)
try:

View file

@ -65,8 +65,7 @@ class LogiCircleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, user_input=None):
"""Handle external yaml configuration."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
self.flow_impl = DOMAIN
@ -76,8 +75,7 @@ class LogiCircleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a flow start."""
flows = self.hass.data.get(DATA_FLOW_IMPL, {})
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
if not flows:
return self.async_abort(reason="missing_configuration")
@ -138,8 +136,7 @@ class LogiCircleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_code(self, code=None):
"""Received code for authentication."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
return await self._async_create_session(code)

View file

@ -14,7 +14,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.core import callback
from .const import (
ABORT_REASON_ALREADY_CONFIGURED,
ABORT_REASON_CANNOT_CONNECT,
BRIDGE_TIMEOUT,
CONF_CA_CERTS,
@ -89,8 +88,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle pairing with the hub."""
errors = {}
# Abort if existing entry with matching host exists.
if self._async_data_host_is_already_configured():
return self.async_abort(reason=ABORT_REASON_ALREADY_CONFIGURED)
self._async_abort_entries_match({CONF_HOST: self.data[CONF_HOST]})
self._configure_tls_assets()
@ -155,15 +153,6 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
for asset_key, conf_key in FILE_MAPPING.items():
self.data[conf_key] = TLS_ASSET_TEMPLATE.format(self.bridge_id, asset_key)
@callback
def _async_data_host_is_already_configured(self):
"""Check to see if the host is already configured."""
return any(
self.data[CONF_HOST] == entry.data[CONF_HOST]
for entry in self._async_current_entries()
if CONF_HOST in entry.data
)
async def async_step_import(self, import_info):
"""Import a new Caseta bridge as a config entry.
@ -174,8 +163,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self.data[CONF_HOST] = host
# Abort if existing entry with matching host exists.
if self._async_data_host_is_already_configured():
return self.async_abort(reason=ABORT_REASON_ALREADY_CONFIGURED)
self._async_abort_entries_match({CONF_HOST: self.data[CONF_HOST]})
self.data[CONF_KEYFILE] = import_info[CONF_KEYFILE]
self.data[CONF_CERTFILE] = import_info[CONF_CERTFILE]

View file

@ -9,7 +9,6 @@ CONF_CA_CERTS = "ca_certs"
STEP_IMPORT_FAILED = "import_failed"
ERROR_CANNOT_CONNECT = "cannot_connect"
ABORT_REASON_CANNOT_CONNECT = "cannot_connect"
ABORT_REASON_ALREADY_CONFIGURED = "already_configured"
BRIDGE_LEAP = "leap"
BRIDGE_LIP = "lip"

View file

@ -121,9 +121,7 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
# Search for duplicates: there isn't a useful unique_id, but
# at least prevent entries with the same motionEye URL.
for existing_entry in self._async_current_entries(include_ignore=False):
if existing_entry.data.get(CONF_URL) == user_input[CONF_URL]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
return self.async_create_entry(
title=f"{user_input[CONF_URL]}",

View file

@ -17,8 +17,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
errors = {}
if user_input is not None:

View file

@ -56,20 +56,6 @@ async def validate_input_owserver(
return {"title": host}
def is_duplicate_owserver_entry(
hass: HomeAssistant, user_input: dict[str, Any]
) -> bool:
"""Check existing entries for matching host and port."""
for config_entry in hass.config_entries.async_entries(DOMAIN):
if (
config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER
and config_entry.data[CONF_HOST] == user_input[CONF_HOST]
and config_entry.data[CONF_PORT] == user_input[CONF_PORT]
):
return True
return False
async def validate_input_mount_dir(
hass: HomeAssistant, data: dict[str, Any]
) -> dict[str, str]:
@ -125,8 +111,13 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN):
errors = {}
if user_input:
# Prevent duplicate entries
if is_duplicate_owserver_entry(self.hass, user_input):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match(
{
CONF_TYPE: CONF_TYPE_OWSERVER,
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
}
)
self.onewire_config.update(user_input)

View file

@ -49,9 +49,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, conf: dict) -> dict:
"""Import a configuration from config.yaml."""
for entry in self._async_current_entries():
if entry.data[CONF_HOST] == conf[CONF_HOST]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: conf[CONF_HOST]})
return await self.async_step_user(
{

View file

@ -113,9 +113,7 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
user_input[CONF_HOST] = self.discovery_info[CONF_HOST]
user_input[CONF_PORT] = self.discovery_info.get(CONF_PORT, DEFAULT_PORT)
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == user_input[CONF_HOST]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
try:
api = await validate_gw_input(self.hass, user_input)

View file

@ -12,7 +12,6 @@ import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant.components.dhcp import IP_ADDRESS
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
from homeassistant.core import callback
from .const import DOMAIN
@ -60,9 +59,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_dhcp(self, discovery_info):
"""Handle dhcp discovery."""
if self._async_ip_address_already_configured(discovery_info[IP_ADDRESS]):
return self.async_abort(reason="already_configured")
self.ip_address = discovery_info[IP_ADDRESS]
self._async_abort_entries_match({CONF_IP_ADDRESS: self.ip_address})
self.ip_address = discovery_info[IP_ADDRESS]
self.context["title_placeholders"] = {CONF_IP_ADDRESS: self.ip_address}
return await self.async_step_user()
@ -111,14 +109,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.ip_address = data[CONF_IP_ADDRESS]
return await self.async_step_user()
@callback
def _async_ip_address_already_configured(self, ip_address):
"""See if we already have an entry matching the ip_address."""
for entry in self._async_current_entries():
if entry.data.get(CONF_IP_ADDRESS) == ip_address:
return True
return False
class WrongVersion(exceptions.HomeAssistantError):
"""Error to indicate the powerwall uses a software version we cannot interact with."""

View file

@ -15,17 +15,6 @@ DATA_SCHEMA = vol.Schema(
async def validate_input(hass: core.HomeAssistant, data):
"""Validate the user host input."""
confs = hass.config_entries.async_entries(DOMAIN)
same_entries = [
True
for entry in confs
if entry.data.get("host") == data["host"]
and entry.data.get("port") == data["port"]
]
if same_entries:
raise ExistingEntry
api_instance = ProgettiHWSWAPI(f'{data["host"]}:{data["port"]}')
is_valid = await api_instance.check_board()
@ -80,13 +69,14 @@ class ProgettiHWSWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors = {}
if user_input is not None:
self._async_abort_entries_match(
{"host": user_input["host"], "port": user_input["port"]}
)
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except ExistingEntry:
return self.async_abort(reason="already_configured")
except Exception: # pylint: disable=broad-except
errors["base"] = "unknown"
else:

View file

@ -79,14 +79,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_homekit(self, discovery_info):
"""Handle HomeKit discovery."""
if self._async_current_entries():
# We can see rachio on the network to tell them to configure
# it, but since the device will not give up the account it is
# bound to and there can be multiple rachio systems on a single
# account, we avoid showing the device as discovered once
# they already have one configured as they can always
# add a new one via "+"
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
properties = {
key.lower(): value for (key, value) in discovery_info["properties"].items()
}

View file

@ -6,7 +6,6 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.typing import DiscoveryInfoType
@ -53,14 +52,6 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Define the config flow to handle options."""
return RainMachineOptionsFlowHandler(config_entry)
@callback
def _async_abort_ip_address_configured(self, ip_address):
"""Abort if we already have an entry for the ip."""
# IP already configured
for entry in self._async_current_entries(include_ignore=False):
if ip_address == entry.data[CONF_IP_ADDRESS]:
raise AbortFlow("already_configured")
async def async_step_homekit(self, discovery_info):
"""Handle a flow initialized by homekit discovery."""
return await self.async_step_zeroconf(discovery_info)
@ -69,7 +60,7 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle discovery via zeroconf."""
ip_address = discovery_info["host"]
self._async_abort_ip_address_configured(ip_address)
self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})
# Handle IP change
for entry in self._async_current_entries(include_ignore=False):
# Try our existing credentials to check for ip change
@ -109,7 +100,9 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the start of the config flow."""
errors = {}
if user_input:
self._async_abort_ip_address_configured(user_input[CONF_IP_ADDRESS])
self._async_abort_entries_match(
{CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS]}
)
controller = await async_get_controller(
self.hass,
user_input[CONF_IP_ADDRESS],

View file

@ -88,8 +88,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
# If we already have the host configured do
# not open connections to it if we can avoid it.
if self._host_already_configured(discovery_info[CONF_HOST]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: discovery_info[CONF_HOST]})
self.discovery_info.update({CONF_HOST: discovery_info[CONF_HOST]})
@ -151,12 +150,3 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
title=self.discovery_info[CONF_NAME],
data=self.discovery_info,
)
def _host_already_configured(self, host):
"""See if we already have a hub with the host address configured."""
existing_hosts = {
entry.data[CONF_HOST]
for entry in self._async_current_entries()
if CONF_HOST in entry.data
}
return host in existing_hosts

View file

@ -79,8 +79,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_dhcp(self, discovery_info):
"""Handle dhcp discovery."""
if self._async_host_already_configured(discovery_info[IP_ADDRESS]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: discovery_info[IP_ADDRESS]})
if not discovery_info[HOSTNAME].startswith(("irobot-", "roomba-")):
return self.async_abort(reason="not_irobot_device")
@ -183,11 +182,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
),
)
if any(
user_input["host"] == entry.data.get("host")
for entry in self._async_current_entries()
):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input["host"]})
self.host = user_input[CONF_HOST]
self.blid = user_input[CONF_BLID].upper()
@ -260,14 +255,6 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors,
)
@callback
def _async_host_already_configured(self, host):
"""See if we already have an entry matching the host."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == host:
return True
return False
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle options."""

View file

@ -60,8 +60,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_dhcp(self, discovery_info):
"""Handle dhcp discovery."""
if self._host_already_configured(discovery_info[IP_ADDRESS]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: discovery_info[IP_ADDRESS]})
formatted_mac = format_mac(discovery_info[MAC_ADDRESS])
await self.async_set_unique_id(format_mac(formatted_mac))
@ -79,8 +78,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
if self._host_already_configured(user_input[CONF_HOST]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
try:
info = await validate_input(self.hass, user_input)
@ -108,18 +106,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, user_input):
"""Handle import."""
if self._host_already_configured(user_input[CONF_HOST]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
return await self.async_step_user(user_input)
def _host_already_configured(self, host):
"""See if we already have an entry matching the host."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == host:
return True
return False
@staticmethod
@callback
def async_get_options_flow(config_entry):

View file

@ -10,7 +10,6 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import ssdp
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.core import callback
from .const import CONF_ENDPOINT, DOMAIN
@ -75,9 +74,7 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_init(self, user_input=None):
"""Handle a flow start."""
# Check if already configured
if self._async_endpoint_already_configured():
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_ENDPOINT: self.conf.endpoint})
if user_input is None:
return self.async_show_form(
step_id="init",
@ -144,11 +141,3 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.conf = SongpalConfig(name, parsed_url.hostname, endpoint)
return await self.async_step_init(user_input)
@callback
def _async_endpoint_already_configured(self):
"""See if we already have an endpoint matching user input configured."""
for entry in self._async_current_entries():
if entry.data.get(CONF_ENDPOINT) == self.conf.endpoint:
return True
return False

View file

@ -37,10 +37,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
error = None
if user_input:
if user_input[CONF_USERNAME] in [
entry.data[CONF_USERNAME] for entry in self._async_current_entries()
]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]})
try:
await self.validate_login_creds(user_input)

View file

@ -82,14 +82,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_homekit(self, discovery_info):
"""Handle HomeKit discovery."""
if self._async_current_entries():
# We can see tado on the network to tell them to configure
# it, but since the device will not give up the account it is
# bound to and there can be multiple tado devices on a single
# account, we avoid showing the device as discovered once
# they already have one configured as they can always
# add a new one via "+"
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
properties = {
key.lower(): value for (key, value) in discovery_info["properties"].items()
}

View file

@ -26,8 +26,7 @@ class TibberConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
if self._async_current_entries():
return self.async_abort(reason="already_configured")
self._async_abort_entries_match()
if user_input is not None:
access_token = user_input[CONF_ACCESS_TOKEN].replace(" ", "")

View file

@ -106,9 +106,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, user_input):
"""Import a config entry."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == user_input["host"]:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: user_input["host"]})
# Happens if user has host directly in configuration.yaml
if "key" not in user_input:

View file

@ -66,10 +66,7 @@ class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN):
errors["base"] = "invalid_address"
return await self._show_setup_form(errors)
entries = self._async_current_entries()
for entry in entries:
if entry.data[CONF_ID] == unique_id:
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_ID: unique_id})
return self.async_create_entry(
title=str(unique_id),

View file

@ -225,8 +225,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
CONF_HOST: parsed_url.hostname,
}
if self._host_already_configured(self.config[CONF_HOST]):
return self.async_abort(reason="already_configured")
self._async_abort_entries_match({CONF_HOST: self.config[CONF_HOST]})
await self.async_set_unique_id(mac_address)
self._abort_if_unique_id_configured(updates=self.config)
@ -242,13 +241,6 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
return await self.async_step_user()
def _host_already_configured(self, host):
"""See if we already have a UniFi entry matching the host."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == host:
return True
return False
class UnifiOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Unifi options."""

View file

@ -52,8 +52,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
)
except CannotConnect:
errors["base"] = "cannot_connect"
except AlreadyConfigured:
return self.async_abort(reason="already_configured")
else:
return await self.async_step_pick_device()
@ -114,8 +112,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except CannotConnect:
_LOGGER.error("Failed to import %s: cannot connect", host)
return self.async_abort(reason="cannot_connect")
except AlreadyConfigured:
return self.async_abort(reason="already_configured")
if CONF_NIGHTLIGHT_SWITCH_TYPE in user_input:
user_input[CONF_NIGHTLIGHT_SWITCH] = (
user_input.pop(CONF_NIGHTLIGHT_SWITCH_TYPE)
@ -125,9 +121,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _async_try_connect(self, host):
"""Set up with options."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == host:
raise AlreadyConfigured
self._async_abort_entries_match({CONF_HOST: host})
bulb = yeelight.Bulb(host)
try:
@ -195,7 +189,3 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class AlreadyConfigured(exceptions.HomeAssistantError):
"""Indicate the ip address is already configured."""

View file

@ -1085,6 +1085,17 @@ class ConfigFlow(data_entry_flow.FlowHandler):
"""Get the options flow for this handler."""
raise data_entry_flow.UnknownHandler
@callback
def _async_abort_entries_match(
self, match_dict: dict[str, Any] | None = None
) -> None:
"""Abort if current entries match all data."""
if match_dict is None:
match_dict = {} # Match any entry
for entry in self._async_current_entries(include_ignore=False):
if all(item in entry.data.items() for item in match_dict.items()):
raise data_entry_flow.AbortFlow("already_configured")
@callback
def _abort_if_unique_id_configured(
self,

View file

@ -2,14 +2,17 @@
from unittest.mock import AsyncMock, patch
import ambiclimate
import pytest
from homeassistant import data_entry_flow
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.ambiclimate import config_flow
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.setup import async_setup_component
from homeassistant.util import aiohttp
from tests.common import MockConfigEntry
async def init_config_flow(hass):
"""Init a configuration flow."""
@ -40,12 +43,15 @@ async def test_abort_if_already_setup(hass):
"""Test we abort if Ambiclimate is already setup."""
flow = await init_config_flow(hass)
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
result = await flow.async_step_user()
MockConfigEntry(domain=config_flow.DOMAIN).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
with pytest.raises(data_entry_flow.AbortFlow):
result = await flow.async_step_code()
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
@ -103,11 +109,11 @@ async def test_abort_invalid_code(hass):
async def test_already_setup(hass):
"""Test when already setup."""
config_flow.register_flow_implementation(hass, None, None)
flow = await init_config_flow(hass)
with patch.object(hass.config_entries, "async_entries", return_value=True):
result = await flow.async_step_user()
MockConfigEntry(domain=config_flow.DOMAIN).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"

View file

@ -1,4 +1,5 @@
"""Tests for emulated_roku config flow."""
from homeassistant import config_entries
from homeassistant.components.emulated_roku import config_flow
from tests.common import MockConfigEntry
@ -6,10 +7,10 @@ from tests.common import MockConfigEntry
async def test_flow_works(hass):
"""Test that config flow works."""
flow = config_flow.EmulatedRokuFlowHandler()
flow.hass = hass
result = await flow.async_step_user(
user_input={"name": "Emulated Roku Test", "listen_port": 8060}
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={"name": "Emulated Roku Test", "listen_port": 8060},
)
assert result["type"] == "create_entry"
@ -22,10 +23,12 @@ async def test_flow_already_registered_entry(hass):
MockConfigEntry(
domain="emulated_roku", data={"name": "Emulated Roku Test", "listen_port": 8062}
).add_to_hass(hass)
flow = config_flow.EmulatedRokuFlowHandler()
flow.hass = hass
result = await flow.async_step_user(
user_input={"name": "Emulated Roku Test", "listen_port": 8062}
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={"name": "Emulated Roku Test", "listen_port": 8062},
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"

View file

@ -5,7 +5,6 @@ from unittest.mock import ANY, AsyncMock, MagicMock, patch
from kostal.plenticore import PlenticoreAuthenticationException
from homeassistant import config_entries, setup
from homeassistant.components.kostal_plenticore import config_flow
from homeassistant.components.kostal_plenticore.const import DOMAIN
from tests.common import MockConfigEntry
@ -188,16 +187,3 @@ async def test_already_configured(hass):
assert result2["type"] == "abort"
assert result2["reason"] == "already_configured"
def test_configured_instances(hass):
"""Test configured_instances returns all configured hosts."""
MockConfigEntry(
domain="kostal_plenticore",
data={"host": "2.2.2.2", "password": "foobar"},
unique_id="112233445566",
).add_to_hass(hass)
result = config_flow.configured_instances(hass)
assert result == {"2.2.2.2"}

View file

@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, Mock, patch
import pytest
from homeassistant import data_entry_flow
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.logi_circle import config_flow
from homeassistant.components.logi_circle.config_flow import (
DOMAIN,
@ -13,7 +13,7 @@ from homeassistant.components.logi_circle.config_flow import (
)
from homeassistant.setup import async_setup_component
from tests.common import mock_coro
from tests.common import MockConfigEntry, mock_coro
class MockRequest:
@ -121,23 +121,25 @@ async def test_abort_if_no_implementation_registered(hass):
async def test_abort_if_already_setup(hass):
"""Test we abort if Logi Circle is already setup."""
flow = init_config_flow(hass)
MockConfigEntry(domain=config_flow.DOMAIN).add_to_hass(hass)
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
result = await flow.async_step_user()
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
result = await flow.async_step_import()
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
with pytest.raises(data_entry_flow.AbortFlow):
result = await flow.async_step_code()
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
with patch.object(hass.config_entries, "async_entries", return_value=[{}]):
result = await flow.async_step_auth()
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "external_setup"

View file

@ -189,7 +189,7 @@ async def test_duplicate_bridge_import(hass):
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == CasetaConfigFlow.ABORT_REASON_ALREADY_CONFIGURED
assert result["reason"] == "already_configured"
assert len(mock_setup_entry.mock_calls) == 0

View file

@ -1,7 +1,8 @@
"""Tests for the Twente Milieu config flow."""
import aiohttp
from homeassistant import data_entry_flow
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.twentemilieu import config_flow
from homeassistant.components.twentemilieu.const import (
CONF_HOUSE_LETTER,
CONF_HOUSE_NUMBER,
@ -83,7 +84,9 @@ async def test_address_already_set_up(
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=FIXTURE_USER_INPUT
config_flow.DOMAIN,
context={"source": config_entries.SOURCE_USER},
data=FIXTURE_USER_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT

View file

@ -2755,3 +2755,60 @@ async def test_setup_retrying_during_shutdown(hass):
await hass.async_block_till_done()
assert len(mock_call.return_value.mock_calls) == 0
@pytest.mark.parametrize(
"matchers, reason",
[
({}, "already_configured"),
({"host": "3.3.3.3"}, "no_match"),
({"host": "3.4.5.6"}, "already_configured"),
({"host": "3.4.5.6", "ip": "3.4.5.6"}, "no_match"),
({"host": "3.4.5.6", "ip": "1.2.3.4"}, "already_configured"),
({"host": "3.4.5.6", "ip": "1.2.3.4", "port": 23}, "already_configured"),
({"ip": "9.9.9.9"}, "already_configured"),
({"ip": "7.7.7.7"}, "no_match"), # ignored
],
)
async def test__async_abort_entries_match(hass, manager, matchers, reason):
"""Test aborting if matching config entries exist."""
MockConfigEntry(
domain="comp", data={"ip": "1.2.3.4", "host": "4.5.6.7", "port": 23}
).add_to_hass(hass)
MockConfigEntry(
domain="comp", data={"ip": "9.9.9.9", "host": "4.5.6.7", "port": 23}
).add_to_hass(hass)
MockConfigEntry(
domain="comp", data={"ip": "1.2.3.4", "host": "3.4.5.6", "port": 23}
).add_to_hass(hass)
MockConfigEntry(
domain="comp",
source=config_entries.SOURCE_IGNORE,
data={"ip": "7.7.7.7", "host": "4.5.6.7", "port": 23},
).add_to_hass(hass)
await async_setup_component(hass, "persistent_notification", {})
mock_setup_entry = AsyncMock(return_value=True)
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
mock_entity_platform(hass, "config_flow.comp", None)
class TestFlow(config_entries.ConfigFlow):
"""Test flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Test user step."""
self._async_abort_entries_match(matchers)
return self.async_abort(reason="no_match")
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
result = await manager.flow.async_init(
"comp", context={"source": config_entries.SOURCE_USER}
)
await hass.async_block_till_done()
assert result["type"] == "abort"
assert result["reason"] == reason