From 34c84a6bbb524812af4378bd45dbd98fd42898cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 May 2021 15:00:12 -0500 Subject: [PATCH] Reduce boilerplate to abort for matching config entries (#50186) Co-authored-by: Franck Nijhof --- .../components/adguard/config_flow.py | 10 +--- .../components/ambiclimate/config_flow.py | 9 +-- .../components/broadlink/config_flow.py | 6 +- .../components/denonavr/config_flow.py | 4 +- .../components/dunehd/config_flow.py | 3 +- .../components/emulated_roku/config_flow.py | 8 +-- .../components/forked_daapd/config_flow.py | 4 +- .../components/foscam/config_flow.py | 10 +--- .../components/fritzbox/config_flow.py | 5 +- .../components/goalzero/config_flow.py | 9 +-- .../components/gogogate2/config_flow.py | 4 +- .../components/hangouts/config_flow.py | 13 +---- .../components/harmony/config_flow.py | 13 +---- homeassistant/components/hue/config_flow.py | 13 +---- .../hunterdouglas_powerview/config_flow.py | 17 +----- .../components/keenetic_ndms2/config_flow.py | 4 +- .../kostal_plenticore/config_flow.py | 14 +---- .../components/litterrobot/config_flow.py | 4 +- .../components/logi_circle/config_flow.py | 9 +-- .../components/lutron_caseta/config_flow.py | 16 +----- .../components/lutron_caseta/const.py | 1 - .../components/motioneye/config_flow.py | 4 +- .../components/mullvad/config_flow.py | 3 +- .../components/onewire/config_flow.py | 23 +++----- .../components/philips_js/config_flow.py | 4 +- .../components/plugwise/config_flow.py | 4 +- .../components/powerwall/config_flow.py | 14 +---- .../components/progettihwsw/config_flow.py | 16 +----- .../components/rachio/config_flow.py | 9 +-- .../components/rainmachine/config_flow.py | 15 ++--- homeassistant/components/roku/config_flow.py | 12 +--- .../components/roomba/config_flow.py | 17 +----- .../components/somfy_mylink/config_flow.py | 17 +----- .../components/songpal/config_flow.py | 13 +---- .../components/subaru/config_flow.py | 5 +- homeassistant/components/tado/config_flow.py | 9 +-- .../components/tibber/config_flow.py | 3 +- .../components/tradfri/config_flow.py | 4 +- .../components/twentemilieu/config_flow.py | 5 +- homeassistant/components/unifi/config_flow.py | 10 +--- .../components/yeelight/config_flow.py | 12 +--- homeassistant/config_entries.py | 11 ++++ .../ambiclimate/test_config_flow.py | 24 +++++--- .../emulated_roku/test_config_flow.py | 19 ++++--- .../kostal_plenticore/test_config_flow.py | 14 ----- .../logi_circle/test_config_flow.py | 24 ++++---- .../lutron_caseta/test_config_flow.py | 2 +- .../twentemilieu/test_config_flow.py | 7 ++- tests/test_config_entries.py | 57 +++++++++++++++++++ 49 files changed, 183 insertions(+), 350 deletions(-) diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index 11d97d98d62..bbb6d34954b 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -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 = {} diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index d714a6bc2a6..7ef0c5439aa 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -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) diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index 4457f4d2675..884a6a9d102 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -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): diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index a58b0ae991f..dc97e81bafd 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -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, diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py index 3094999dc62..cbb248410e0 100644 --- a/homeassistant/components/dunehd/config_flow.py +++ b/homeassistant/components/dunehd/config_flow.py @@ -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) diff --git a/homeassistant/components/emulated_roku/config_flow.py b/homeassistant/components/emulated_roku/config_flow.py index f1c00a7f8b4..dd7cd87c96a 100644 --- a/homeassistant/components/emulated_roku/config_flow.py +++ b/homeassistant/components/emulated_roku/config_flow.py @@ -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)) diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index 1d01218b776..16ebc1f82f7 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -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") diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index 23d5b335edc..0ab4e5d9866 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -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], diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 79763d18d2a..ecbcfa0bf68 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -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] diff --git a/homeassistant/components/goalzero/config_flow.py b/homeassistant/components/goalzero/config_flow.py index c570554d50e..269885e5c3a 100644 --- a/homeassistant/components/goalzero/config_flow.py +++ b/homeassistant/components/goalzero/config_flow.py @@ -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) diff --git a/homeassistant/components/gogogate2/config_flow.py b/homeassistant/components/gogogate2/config_flow.py index bfe740ecaa5..b70a6120153 100644 --- a/homeassistant/components/gogogate2/config_flow.py +++ b/homeassistant/components/gogogate2/config_flow.py @@ -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 diff --git a/homeassistant/components/hangouts/config_flow.py b/homeassistant/components/hangouts/config_flow.py index 2f0dafba0c3..adf62d348f4 100644 --- a/homeassistant/components/hangouts/config_flow.py +++ b/homeassistant/components/hangouts/config_flow.py @@ -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] diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index d6ffa3d1787..be765ee4cb0 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -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 = {} diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 7615185b5c4..4a7ebd01fbd 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -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() diff --git a/homeassistant/components/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py index 6ff7a0027ba..3ae60d5e62e 100644 --- a/homeassistant/components/hunterdouglas_powerview/config_flow.py +++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py @@ -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.""" diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py index 7ce03a1bbc5..4437aa12edf 100644 --- a/homeassistant/components/keenetic_ndms2/config_flow.py +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -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( diff --git a/homeassistant/components/kostal_plenticore/config_flow.py b/homeassistant/components/kostal_plenticore/config_flow.py index 7e3eb55e630..359efef651a 100644 --- a/homeassistant/components/kostal_plenticore/config_flow.py +++ b/homeassistant/components/kostal_plenticore/config_flow.py @@ -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: diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index d30cd868162..c49d18c5257 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -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: diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index ec21d3f6726..ff0fd83ff5b 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -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) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index f48dc19b1d7..ec7010295ec 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -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] diff --git a/homeassistant/components/lutron_caseta/const.py b/homeassistant/components/lutron_caseta/const.py index 40a5d2b01fd..89472f3366b 100644 --- a/homeassistant/components/lutron_caseta/const.py +++ b/homeassistant/components/lutron_caseta/const.py @@ -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" diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py index acba3e16bb2..a8562189d1f 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -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]}", diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index ef737581faf..1b330d4f6a3 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -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: diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 856eb95aa57..468aa6b9acf 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -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) diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 327fa135e61..84303e6ca92 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -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( { diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 6ad06f3bfd5..d19c3b49920 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -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) diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 538382542ef..bd4e49f45d3 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -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.""" diff --git a/homeassistant/components/progettihwsw/config_flow.py b/homeassistant/components/progettihwsw/config_flow.py index 9dcda920a64..89d5916a3fd 100644 --- a/homeassistant/components/progettihwsw/config_flow.py +++ b/homeassistant/components/progettihwsw/config_flow.py @@ -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: diff --git a/homeassistant/components/rachio/config_flow.py b/homeassistant/components/rachio/config_flow.py index 7d73344aadc..b664ebcd45d 100644 --- a/homeassistant/components/rachio/config_flow.py +++ b/homeassistant/components/rachio/config_flow.py @@ -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() } diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index 9febb2b3a92..55ff68c5ea0 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -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], diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index 0accf352fb1..470dccbe37f 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -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 diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 0f3bc44eba6..c3ccd051dd8 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -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.""" diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index e97fe088173..88185629b27 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -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): diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index ae7c9406a92..1475e51afb5 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -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 diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 980608893ac..0c41a478c04 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -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) diff --git a/homeassistant/components/tado/config_flow.py b/homeassistant/components/tado/config_flow.py index 0b9d9fef9ad..7359273e2de 100644 --- a/homeassistant/components/tado/config_flow.py +++ b/homeassistant/components/tado/config_flow.py @@ -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() } diff --git a/homeassistant/components/tibber/config_flow.py b/homeassistant/components/tibber/config_flow.py index 8e82cbdf733..1c1cef88776 100644 --- a/homeassistant/components/tibber/config_flow.py +++ b/homeassistant/components/tibber/config_flow.py @@ -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(" ", "") diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 0c1fa9bb744..1a6ae8706f2 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -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: diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 6d665154778..870ce591788 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -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), diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 8f90e13c9fa..5edc71f9f5d 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -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.""" diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 98767860fd8..3c794792ac3 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -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.""" diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 8c9ad8da4c5..c586fcad79f 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -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, diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index 3a325490064..2da550afd42 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -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" diff --git a/tests/components/emulated_roku/test_config_flow.py b/tests/components/emulated_roku/test_config_flow.py index 879d95d0cfc..23c807cbfa3 100644 --- a/tests/components/emulated_roku/test_config_flow.py +++ b/tests/components/emulated_roku/test_config_flow.py @@ -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" diff --git a/tests/components/kostal_plenticore/test_config_flow.py b/tests/components/kostal_plenticore/test_config_flow.py index 7ce95f71e8e..2e9b25f5721 100644 --- a/tests/components/kostal_plenticore/test_config_flow.py +++ b/tests/components/kostal_plenticore/test_config_flow.py @@ -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"} diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py index 28335b93ad9..dbd35469d79 100644 --- a/tests/components/logi_circle/test_config_flow.py +++ b/tests/components/logi_circle/test_config_flow.py @@ -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,24 +121,26 @@ 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() + result = await flow.async_step_auth() assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "external_setup" diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 14adefc37db..36e86778d0a 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -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 diff --git a/tests/components/twentemilieu/test_config_flow.py b/tests/components/twentemilieu/test_config_flow.py index 93ee7116f76..e4e4b0c8335 100644 --- a/tests/components/twentemilieu/test_config_flow.py +++ b/tests/components/twentemilieu/test_config_flow.py @@ -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 diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 0c12e69364c..77f4fdd0361 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -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