diff --git a/homeassistant/components/airgradient/manifest.json b/homeassistant/components/airgradient/manifest.json index afaf2698ced..3011e0602c9 100644 --- a/homeassistant/components/airgradient/manifest.json +++ b/homeassistant/components/airgradient/manifest.json @@ -6,6 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/airgradient", "integration_type": "device", "iot_class": "local_polling", + "quality_scale": "platinum", "requirements": ["airgradient==0.9.2"], "zeroconf": ["_airgradient._tcp.local."] } diff --git a/homeassistant/components/airgradient/quality_scale.yaml b/homeassistant/components/airgradient/quality_scale.yaml index 7a7f8d5ee1d..ec2e200b0a7 100644 --- a/homeassistant/components/airgradient/quality_scale.yaml +++ b/homeassistant/components/airgradient/quality_scale.yaml @@ -14,9 +14,9 @@ rules: status: exempt comment: | This integration does not provide additional actions. - docs-high-level-description: todo - docs-installation-instructions: todo - docs-removal-instructions: todo + docs-high-level-description: done + docs-installation-instructions: done + docs-removal-instructions: done entity-event-setup: status: exempt comment: | @@ -34,7 +34,7 @@ rules: docs-configuration-parameters: status: exempt comment: No options to configure - docs-installation-parameters: todo + docs-installation-parameters: done entity-unavailable: done integration-owner: done log-when-unavailable: done @@ -43,23 +43,19 @@ rules: status: exempt comment: | This integration does not require authentication. - test-coverage: todo + test-coverage: done # Gold devices: done diagnostics: done - discovery-update-info: - status: todo - comment: DHCP is still possible - discovery: - status: todo - comment: DHCP is still possible - docs-data-update: todo - docs-examples: todo - docs-known-limitations: todo - docs-supported-devices: todo - docs-supported-functions: todo - docs-troubleshooting: todo - docs-use-cases: todo + discovery-update-info: done + discovery: done + docs-data-update: done + docs-examples: done + docs-known-limitations: done + docs-supported-devices: done + docs-supported-functions: done + docs-troubleshooting: done + docs-use-cases: done dynamic-devices: status: exempt comment: | diff --git a/homeassistant/components/airnow/__init__.py b/homeassistant/components/airnow/__init__.py index 6fb7e90502f..2881469b968 100644 --- a/homeassistant/components/airnow/__init__.py +++ b/homeassistant/components/airnow/__init__.py @@ -45,9 +45,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bo # Store Entity and Initialize Platforms entry.runtime_data = coordinator - # Listen for option changes - entry.async_on_unload(entry.add_update_listener(update_listener)) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) # Clean up unused device entries with no entities @@ -88,8 +85,3 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/airnow/config_flow.py b/homeassistant/components/airnow/config_flow.py index 7cd113125a8..661e1b0a298 100644 --- a/homeassistant/components/airnow/config_flow.py +++ b/homeassistant/components/airnow/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS from homeassistant.core import HomeAssistant, callback @@ -126,7 +126,7 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN): return AirNowOptionsFlowHandler() -class AirNowOptionsFlowHandler(OptionsFlow): +class AirNowOptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow for AirNow.""" async def async_step_init( diff --git a/homeassistant/components/analytics_insights/__init__.py b/homeassistant/components/analytics_insights/__init__.py index ee7f6611c65..2d66d5149cf 100644 --- a/homeassistant/components/analytics_insights/__init__.py +++ b/homeassistant/components/analytics_insights/__init__.py @@ -55,7 +55,6 @@ async def async_setup_entry( entry.runtime_data = AnalyticsInsightsData(coordinator=coordinator, names=names) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -65,10 +64,3 @@ async def async_unload_entry( ) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def update_listener( - hass: HomeAssistant, entry: AnalyticsInsightsConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py index b2648f7c13c..d5c0c4a7f73 100644 --- a/homeassistant/components/analytics_insights/config_flow.py +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -11,7 +11,11 @@ from python_homeassistant_analytics import ( from python_homeassistant_analytics.models import Environment, IntegrationType import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( @@ -129,7 +133,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): ) -class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlow): +class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithReload): """Handle Homeassistant Analytics options.""" async def async_step_init( diff --git a/homeassistant/components/androidtv_remote/__init__.py b/homeassistant/components/androidtv_remote/__init__.py index c8556b6da90..328ac863e46 100644 --- a/homeassistant/components/androidtv_remote/__init__.py +++ b/homeassistant/components/androidtv_remote/__init__.py @@ -68,7 +68,6 @@ async def async_setup_entry( entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) ) - entry.async_on_unload(entry.add_update_listener(async_update_options)) entry.async_on_unload(api.disconnect) return True @@ -80,13 +79,3 @@ async def async_unload_entry( """Unload a config entry.""" _LOGGER.debug("async_unload_entry: %s", entry.data) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def async_update_options( - hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry -) -> None: - """Handle options update.""" - _LOGGER.debug( - "async_update_options: data: %s options: %s", entry.data, entry.options - ) - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/androidtv_remote/config_flow.py b/homeassistant/components/androidtv_remote/config_flow.py index 351cae61b1d..0a236c7c9ef 100644 --- a/homeassistant/components/androidtv_remote/config_flow.py +++ b/homeassistant/components/androidtv_remote/config_flow.py @@ -19,7 +19,7 @@ from homeassistant.config_entries import ( SOURCE_RECONFIGURE, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.core import callback @@ -116,10 +116,10 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): pin = user_input["pin"] await self.api.async_finish_pairing(pin) if self.source == SOURCE_REAUTH: - await self.hass.config_entries.async_reload( - self._get_reauth_entry().entry_id + return self.async_update_reload_and_abort( + self._get_reauth_entry(), reload_even_if_entry_is_unchanged=True ) - return self.async_abort(reason="reauth_successful") + return self.async_create_entry( title=self.name, data={ @@ -243,7 +243,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): return AndroidTVRemoteOptionsFlowHandler(config_entry) -class AndroidTVRemoteOptionsFlowHandler(OptionsFlow): +class AndroidTVRemoteOptionsFlowHandler(OptionsFlowWithReload): """Android TV Remote options flow.""" def __init__(self, config_entry: AndroidTVRemoteConfigEntry) -> None: diff --git a/homeassistant/components/bauknecht/__init__.py b/homeassistant/components/bauknecht/__init__.py new file mode 100644 index 00000000000..1e93f1ab0c2 --- /dev/null +++ b/homeassistant/components/bauknecht/__init__.py @@ -0,0 +1 @@ +"""Bauknecht virtual integration.""" diff --git a/homeassistant/components/bauknecht/manifest.json b/homeassistant/components/bauknecht/manifest.json new file mode 100644 index 00000000000..b875d7fbc31 --- /dev/null +++ b/homeassistant/components/bauknecht/manifest.json @@ -0,0 +1,6 @@ +{ + "domain": "bauknecht", + "name": "Bauknecht", + "integration_type": "virtual", + "supported_by": "whirlpool" +} diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index 3d84d6edd69..59216e4a863 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -54,16 +54,20 @@ class Control4RuntimeData: type Control4ConfigEntry = ConfigEntry[Control4RuntimeData] -async def call_c4_api_retry(func, *func_args): # noqa: RET503 +async def call_c4_api_retry(func, *func_args): """Call C4 API function and retry on failure.""" - # Ruff doesn't understand this loop - the exception is always raised after the retries + exc = None for i in range(API_RETRY_TIMES): try: return await func(*func_args) except client_exceptions.ClientError as exception: - _LOGGER.error("Error connecting to Control4 account API: %s", exception) - if i == API_RETRY_TIMES - 1: - raise ConfigEntryNotReady(exception) from exception + _LOGGER.error( + "Try: %d, Error connecting to Control4 account API: %s", + i + 1, + exception, + ) + exc = exception + raise ConfigEntryNotReady(exc) from exc async def async_setup_entry(hass: HomeAssistant, entry: Control4ConfigEntry) -> bool: @@ -141,21 +145,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: Control4ConfigEntry) -> ui_configuration=ui_configuration, ) - entry.async_on_unload(entry.add_update_listener(update_listener)) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def update_listener( - hass: HomeAssistant, config_entry: Control4ConfigEntry -) -> None: - """Update when config_entry options update.""" - _LOGGER.debug("Config entry was updated, rerunning setup") - await hass.config_entries.async_reload(config_entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: Control4ConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/control4/config_flow.py b/homeassistant/components/control4/config_flow.py index 3ca96ca4e52..9d5df61b513 100644 --- a/homeassistant/components/control4/config_flow.py +++ b/homeassistant/components/control4/config_flow.py @@ -11,7 +11,11 @@ from pyControl4.director import C4Director from pyControl4.error_handling import NotFound, Unauthorized import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -153,7 +157,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN): return OptionsFlowHandler() -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow for Control4.""" async def async_step_init( diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index da2b601317a..8cead5f4992 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -53,8 +53,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: DenonavrConfigEntry) -> raise ConfigEntryNotReady from ex receiver = connect_denonavr.receiver - entry.async_on_unload(entry.add_update_listener(update_listener)) - entry.runtime_data = receiver await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -100,10 +98,3 @@ async def async_unload_entry( _LOGGER.debug("Removing zone3 from DenonAvr") return unload_ok - - -async def update_listener( - hass: HomeAssistant, config_entry: DenonavrConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index 930d0e009ac..204471a13b4 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -10,7 +10,11 @@ import denonavr from denonavr.exceptions import AvrNetworkError, AvrTimoutError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_TYPE from homeassistant.core import callback from homeassistant.helpers.httpx_client import get_async_client @@ -51,7 +55,7 @@ DEFAULT_USE_TELNET_NEW_INSTALL = True CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str}) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Options for the component.""" async def async_step_init( diff --git a/homeassistant/components/dnsip/__init__.py b/homeassistant/components/dnsip/__init__.py index 37e0f60849f..3487ce83c7b 100644 --- a/homeassistant/components/dnsip/__init__.py +++ b/homeassistant/components/dnsip/__init__.py @@ -13,15 +13,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up DNS IP from a config entry.""" await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload dnsip config entry.""" diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index ab1ca42acd3..0ea2a9d092b 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_NAME, CONF_PORT from homeassistant.core import callback @@ -165,7 +165,7 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN): ) -class DnsIPOptionsFlowHandler(OptionsFlow): +class DnsIPOptionsFlowHandler(OptionsFlowWithReload): """Handle a option config flow for dnsip integration.""" async def async_step_init( diff --git a/homeassistant/components/emoncms/__init__.py b/homeassistant/components/emoncms/__init__.py index 012abcc8c9a..1c081dc86e6 100644 --- a/homeassistant/components/emoncms/__init__.py +++ b/homeassistant/components/emoncms/__init__.py @@ -69,16 +69,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: EmonCMSConfigEntry) -> b await coordinator.async_config_entry_first_refresh() entry.runtime_data = coordinator - entry.async_on_unload(entry.add_update_listener(update_listener)) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def update_listener(hass: HomeAssistant, entry: EmonCMSConfigEntry): - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: EmonCMSConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/emoncms/config_flow.py b/homeassistant/components/emoncms/config_flow.py index b14903a78f9..375077a83d4 100644 --- a/homeassistant/components/emoncms/config_flow.py +++ b/homeassistant/components/emoncms/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_API_KEY, CONF_URL from homeassistant.core import callback @@ -221,7 +221,7 @@ class EmoncmsConfigFlow(ConfigFlow, domain=DOMAIN): ) -class EmoncmsOptionsFlow(OptionsFlow): +class EmoncmsOptionsFlow(OptionsFlowWithReload): """Emoncms Options flow handler.""" def __init__(self, config_entry: ConfigEntry) -> None: diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 903aaea9980..e83ab16064c 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -17,7 +17,7 @@ "mqtt": ["esphome/discover/#"], "quality_scale": "platinum", "requirements": [ - "aioesphomeapi==36.0.1", + "aioesphomeapi==37.0.2", "esphome-dashboard-api==1.3.0", "bleak-esphome==3.1.0" ], diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index 57c58d3a2b1..9acec01ee6d 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -32,8 +32,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry) - await coordinator.async_config_entry_first_refresh() - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) - return True @@ -46,10 +44,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry) if len(entries) == 1: hass.data.pop(MY_KEY) return await hass.config_entries.async_unload_platforms(entry, [Platform.EVENT]) - - -async def _async_update_listener( - hass: HomeAssistant, entry: FeedReaderConfigEntry -) -> None: - """Handle reconfiguration.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/feedreader/config_flow.py b/homeassistant/components/feedreader/config_flow.py index 3d0fec1a6f5..37c627f21ba 100644 --- a/homeassistant/components/feedreader/config_flow.py +++ b/homeassistant/components/feedreader/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback @@ -44,7 +44,7 @@ class FeedReaderConfigFlow(ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: ConfigEntry, - ) -> OptionsFlow: + ) -> FeedReaderOptionsFlowHandler: """Get the options flow for this handler.""" return FeedReaderOptionsFlowHandler() @@ -119,11 +119,10 @@ class FeedReaderConfigFlow(ConfigFlow, domain=DOMAIN): errors={"base": "url_error"}, ) - self.hass.config_entries.async_update_entry(reconfigure_entry, data=user_input) - return self.async_abort(reason="reconfigure_successful") + return self.async_update_reload_and_abort(reconfigure_entry, data=user_input) -class FeedReaderOptionsFlowHandler(OptionsFlow): +class FeedReaderOptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow.""" async def async_step_init( diff --git a/homeassistant/components/file/__init__.py b/homeassistant/components/file/__init__.py index 7bc206057c8..59a08715b8e 100644 --- a/homeassistant/components/file/__init__.py +++ b/homeassistant/components/file/__init__.py @@ -29,7 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups( entry, [Platform(entry.data[CONF_PLATFORM])] ) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -41,11 +40,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate config entry.""" if config_entry.version > 2: diff --git a/homeassistant/components/file/config_flow.py b/homeassistant/components/file/config_flow.py index 1c4fdbe5c84..9078a4d115e 100644 --- a/homeassistant/components/file/config_flow.py +++ b/homeassistant/components/file/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_FILE_PATH, @@ -131,7 +131,7 @@ class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN): return await self._async_handle_step(Platform.SENSOR.value, user_input) -class FileOptionsFlowHandler(OptionsFlow): +class FileOptionsFlowHandler(OptionsFlowWithReload): """Handle File options.""" async def async_step_init( diff --git a/homeassistant/components/forecast_solar/__init__.py b/homeassistant/components/forecast_solar/__init__.py index 171341f7226..7b534b80500 100644 --- a/homeassistant/components/forecast_solar/__init__.py +++ b/homeassistant/components/forecast_solar/__init__.py @@ -47,8 +47,6 @@ async def async_setup_entry( await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(async_update_options)) - return True @@ -57,10 +55,3 @@ async def async_unload_entry( ) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def async_update_options( - hass: HomeAssistant, entry: ForecastSolarConfigEntry -) -> None: - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/forecast_solar/config_flow.py b/homeassistant/components/forecast_solar/config_flow.py index 9a64ce6e1fb..031764a0d0a 100644 --- a/homeassistant/components/forecast_solar/config_flow.py +++ b/homeassistant/components/forecast_solar/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback @@ -88,7 +88,7 @@ class ForecastSolarFlowHandler(ConfigFlow, domain=DOMAIN): ) -class ForecastSolarOptionFlowHandler(OptionsFlow): +class ForecastSolarOptionFlowHandler(OptionsFlowWithReload): """Handle options.""" async def async_step_init( diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py index faf82b4b516..94f4f8ba0d8 100644 --- a/homeassistant/components/fritz/__init__.py +++ b/homeassistant/components/fritz/__init__.py @@ -75,8 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo if FRITZ_DATA_KEY not in hass.data: hass.data[FRITZ_DATA_KEY] = FritzData() - entry.async_on_unload(entry.add_update_listener(update_listener)) - # Load the other platforms like switch await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -94,9 +92,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bo hass.data.pop(FRITZ_DATA_KEY) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def update_listener(hass: HomeAssistant, entry: FritzConfigEntry) -> None: - """Update when config_entry options update.""" - if entry.options: - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index 2c22a35c4dd..270e9870c63 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -17,7 +17,11 @@ from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, ) -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -409,7 +413,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): ) -class FritzBoxToolsOptionsFlowHandler(OptionsFlow): +class FritzBoxToolsOptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow.""" async def async_step_init( diff --git a/homeassistant/components/fritzbox_callmonitor/__init__.py b/homeassistant/components/fritzbox_callmonitor/__init__.py index b1b5db48216..ea4bf46f09c 100644 --- a/homeassistant/components/fritzbox_callmonitor/__init__.py +++ b/homeassistant/components/fritzbox_callmonitor/__init__.py @@ -48,7 +48,6 @@ async def async_setup_entry( raise ConfigEntryNotReady from ex config_entry.runtime_data = fritzbox_phonebook - config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) return True @@ -59,10 +58,3 @@ async def async_unload_entry( ) -> bool: """Unloading the fritzbox_callmonitor platforms.""" return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) - - -async def update_listener( - hass: HomeAssistant, config_entry: FritzBoxCallMonitorConfigEntry -) -> None: - """Update listener to reload after option has changed.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 8435eff3e18..25e25336d57 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -15,7 +15,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import callback @@ -263,7 +263,7 @@ class FritzBoxCallMonitorConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") -class FritzBoxCallMonitorOptionsFlowHandler(OptionsFlow): +class FritzBoxCallMonitorOptionsFlowHandler(OptionsFlowWithReload): """Handle a fritzbox_callmonitor options flow.""" @classmethod diff --git a/homeassistant/components/gardena_bluetooth/number.py b/homeassistant/components/gardena_bluetooth/number.py index 41b4f1e79ba..342061c18d1 100644 --- a/homeassistant/components/gardena_bluetooth/number.py +++ b/homeassistant/components/gardena_bluetooth/number.py @@ -13,6 +13,7 @@ from gardena_bluetooth.parse import ( ) from homeassistant.components.number import ( + NumberDeviceClass, NumberEntity, NumberEntityDescription, NumberMode, @@ -54,6 +55,7 @@ DESCRIPTIONS = ( native_step=60, entity_category=EntityCategory.CONFIG, char=Valve.manual_watering_time, + device_class=NumberDeviceClass.DURATION, ), GardenaBluetoothNumberEntityDescription( key=Valve.remaining_open_time.uuid, @@ -64,6 +66,7 @@ DESCRIPTIONS = ( native_step=60.0, entity_category=EntityCategory.DIAGNOSTIC, char=Valve.remaining_open_time, + device_class=NumberDeviceClass.DURATION, ), GardenaBluetoothNumberEntityDescription( key=DeviceConfiguration.rain_pause.uuid, @@ -75,6 +78,7 @@ DESCRIPTIONS = ( native_step=6 * 60.0, entity_category=EntityCategory.CONFIG, char=DeviceConfiguration.rain_pause, + device_class=NumberDeviceClass.DURATION, ), GardenaBluetoothNumberEntityDescription( key=DeviceConfiguration.seasonal_adjust.uuid, @@ -86,6 +90,7 @@ DESCRIPTIONS = ( native_step=1.0, entity_category=EntityCategory.CONFIG, char=DeviceConfiguration.seasonal_adjust, + device_class=NumberDeviceClass.DURATION, ), GardenaBluetoothNumberEntityDescription( key=Sensor.threshold.uuid, @@ -153,6 +158,7 @@ class GardenaBluetoothRemainingOpenSetNumber(GardenaBluetoothEntity, NumberEntit _attr_native_min_value = 0.0 _attr_native_max_value = 24 * 60 _attr_native_step = 1.0 + _attr_device_class = NumberDeviceClass.DURATION def __init__( self, diff --git a/homeassistant/components/gardena_bluetooth/valve.py b/homeassistant/components/gardena_bluetooth/valve.py index 4138c7c4472..247a85f93f1 100644 --- a/homeassistant/components/gardena_bluetooth/valve.py +++ b/homeassistant/components/gardena_bluetooth/valve.py @@ -6,7 +6,11 @@ from typing import Any from gardena_bluetooth.const import Valve -from homeassistant.components.valve import ValveEntity, ValveEntityFeature +from homeassistant.components.valve import ( + ValveDeviceClass, + ValveEntity, + ValveEntityFeature, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -37,6 +41,7 @@ class GardenaBluetoothValve(GardenaBluetoothEntity, ValveEntity): _attr_is_closed: bool | None = None _attr_reports_position = False _attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE + _attr_device_class = ValveDeviceClass.WATER characteristics = { Valve.state.uuid, diff --git a/homeassistant/components/github/__init__.py b/homeassistant/components/github/__init__.py index dea2acf4f1b..df50039b03f 100644 --- a/homeassistant/components/github/__init__.py +++ b/homeassistant/components/github/__init__.py @@ -47,7 +47,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: GithubConfigEntry) -> bo async_cleanup_device_registry(hass=hass, entry=entry) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True @@ -87,8 +86,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: GithubConfigEntry) -> b coordinator.unsubscribe() return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def async_reload_entry(hass: HomeAssistant, entry: GithubConfigEntry) -> None: - """Handle an options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/github/config_flow.py b/homeassistant/components/github/config_flow.py index 17338119b9f..a2a7e56830f 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -19,7 +19,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.core import HomeAssistant, callback @@ -214,7 +214,7 @@ class GitHubConfigFlow(ConfigFlow, domain=DOMAIN): return OptionsFlowHandler() -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow for GitHub.""" async def async_step_init( diff --git a/homeassistant/components/holiday/__init__.py b/homeassistant/components/holiday/__init__.py index b364f2c67a4..f0c340785cf 100644 --- a/homeassistant/components/holiday/__init__.py +++ b/homeassistant/components/holiday/__init__.py @@ -34,16 +34,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/holiday/config_flow.py b/homeassistant/components/holiday/config_flow.py index 538d9971109..e9f16a9e4c5 100644 --- a/homeassistant/components/holiday/config_flow.py +++ b/homeassistant/components/holiday/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_COUNTRY from homeassistant.core import callback @@ -227,7 +227,7 @@ class HolidayConfigFlow(ConfigFlow, domain=DOMAIN): ) -class HolidayOptionsFlowHandler(OptionsFlow): +class HolidayOptionsFlowHandler(OptionsFlowWithReload): """Handle Holiday options.""" async def async_step_init( diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index 036ffa286a3..14b5ac39310 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", "iot_class": "cloud_push", "loggers": ["homematicip"], - "requirements": ["homematicip==2.0.7"] + "requirements": ["homematicip==2.2.0"] } diff --git a/homeassistant/components/huum/binary_sensor.py b/homeassistant/components/huum/binary_sensor.py new file mode 100644 index 00000000000..a8e094dda94 --- /dev/null +++ b/homeassistant/components/huum/binary_sensor.py @@ -0,0 +1,42 @@ +"""Sensor for door state.""" + +from __future__ import annotations + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .coordinator import HuumConfigEntry, HuumDataUpdateCoordinator +from .entity import HuumBaseEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: HuumConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up door sensor.""" + async_add_entities( + [HuumDoorSensor(config_entry.runtime_data)], + ) + + +class HuumDoorSensor(HuumBaseEntity, BinarySensorEntity): + """Representation of a BinarySensor.""" + + _attr_name = "Door" + _attr_device_class = BinarySensorDeviceClass.DOOR + + def __init__(self, coordinator: HuumDataUpdateCoordinator) -> None: + """Initialize the BinarySensor.""" + super().__init__(coordinator) + + self._attr_unique_id = f"{coordinator.config_entry.entry_id}_door" + + @property + def is_on(self) -> bool | None: + """Return the current value.""" + return not self.coordinator.data.door_closed diff --git a/homeassistant/components/huum/const.py b/homeassistant/components/huum/const.py index 69dea45b218..6691a2ad8b3 100644 --- a/homeassistant/components/huum/const.py +++ b/homeassistant/components/huum/const.py @@ -4,4 +4,4 @@ from homeassistant.const import Platform DOMAIN = "huum" -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE] diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index ec73d960140..8e01b6b6ae0 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -79,13 +79,6 @@ async def async_setup_entry( await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - async def update_listener( - hass: HomeAssistant, config_entry: JewishCalendarConfigEntry - ) -> None: - # Trigger update of states for all platforms - await hass.config_entries.async_reload(config_entry.entry_id) - - config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) return True diff --git a/homeassistant/components/jewish_calendar/config_flow.py b/homeassistant/components/jewish_calendar/config_flow.py index e896bc90c9e..f52e14537b3 100644 --- a/homeassistant/components/jewish_calendar/config_flow.py +++ b/homeassistant/components/jewish_calendar/config_flow.py @@ -9,7 +9,11 @@ import zoneinfo from hdate.translator import Language import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import ( CONF_ELEVATION, CONF_LANGUAGE, @@ -124,7 +128,7 @@ class JewishCalendarConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_update_reload_and_abort(reconfigure_entry, data=user_input) -class JewishCalendarOptionsFlowHandler(OptionsFlow): +class JewishCalendarOptionsFlowHandler(OptionsFlowWithReload): """Handle Jewish Calendar options.""" async def async_step_init( diff --git a/homeassistant/components/kitchen_sink/__init__.py b/homeassistant/components/kitchen_sink/__init__.py index 2f876ca855d..8b81cd49279 100644 --- a/homeassistant/components/kitchen_sink/__init__.py +++ b/homeassistant/components/kitchen_sink/__init__.py @@ -101,19 +101,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Start a reauth flow entry.async_start_reauth(hass) - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) - # Notify backup listeners hass.async_create_task(_notify_backup_listeners(hass), eager_start=False) return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload config entry.""" # Notify backup listeners diff --git a/homeassistant/components/kitchen_sink/config_flow.py b/homeassistant/components/kitchen_sink/config_flow.py index aa722d27944..059fd11999f 100644 --- a/homeassistant/components/kitchen_sink/config_flow.py +++ b/homeassistant/components/kitchen_sink/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ConfigFlow, ConfigFlowResult, ConfigSubentryFlow, - OptionsFlow, + OptionsFlowWithReload, SubentryFlowResult, ) from homeassistant.core import callback @@ -65,7 +65,7 @@ class KitchenSinkConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle options.""" async def async_step_init( diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 6fa4c8146ba..ead846735c9 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -120,8 +120,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[KNX_MODULE_KEY] = knx_module - entry.async_on_unload(entry.add_update_listener(async_update_entry)) - if CONF_KNX_EXPOSE in config: for expose_config in config[CONF_KNX_EXPOSE]: knx_module.exposures.append( @@ -174,11 +172,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_update_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update a given config entry.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Remove a config entry.""" diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 796c4c60201..7772f366493 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -23,7 +23,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import callback @@ -899,7 +899,7 @@ class KNXConfigFlow(ConfigFlow, domain=DOMAIN): ) -class KNXOptionsFlow(OptionsFlow): +class KNXOptionsFlow(OptionsFlowWithReload): """Handle KNX options.""" def __init__(self, config_entry: ConfigEntry) -> None: diff --git a/homeassistant/components/lamarzocco/__init__.py b/homeassistant/components/lamarzocco/__init__.py index 2d68b3be345..92184b4ac51 100644 --- a/homeassistant/components/lamarzocco/__init__.py +++ b/homeassistant/components/lamarzocco/__init__.py @@ -154,13 +154,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - async def update_listener( - hass: HomeAssistant, entry: LaMarzoccoConfigEntry - ) -> None: - await hass.config_entries.async_reload(entry.entry_id) - - entry.async_on_unload(entry.add_update_listener(update_listener)) - return True diff --git a/homeassistant/components/lamarzocco/config_flow.py b/homeassistant/components/lamarzocco/config_flow.py index e352e337d0b..fb968a0b4af 100644 --- a/homeassistant/components/lamarzocco/config_flow.py +++ b/homeassistant/components/lamarzocco/config_flow.py @@ -21,7 +21,7 @@ from homeassistant.config_entries import ( SOURCE_RECONFIGURE, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_ADDRESS, @@ -363,7 +363,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): return LmOptionsFlowHandler() -class LmOptionsFlowHandler(OptionsFlow): +class LmOptionsFlowHandler(OptionsFlowWithReload): """Handles options flow for the component.""" async def async_step_init( diff --git a/homeassistant/components/lastfm/__init__.py b/homeassistant/components/lastfm/__init__.py index b5a4612429e..90bee0cf4e7 100644 --- a/homeassistant/components/lastfm/__init__.py +++ b/homeassistant/components/lastfm/__init__.py @@ -16,7 +16,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: LastFMConfigEntry) -> bo entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -24,8 +23,3 @@ async def async_setup_entry(hass: HomeAssistant, entry: LastFMConfigEntry) -> bo async def async_unload_entry(hass: HomeAssistant, entry: LastFMConfigEntry) -> bool: """Unload lastfm config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def update_listener(hass: HomeAssistant, entry: LastFMConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/lastfm/config_flow.py b/homeassistant/components/lastfm/config_flow.py index 422c50a5fb9..47c5b0e217e 100644 --- a/homeassistant/components/lastfm/config_flow.py +++ b/homeassistant/components/lastfm/config_flow.py @@ -8,7 +8,11 @@ from typing import Any from pylast import LastFMNetwork, PyLastError, User, WSError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_API_KEY from homeassistant.core import callback from homeassistant.helpers.selector import ( @@ -155,7 +159,7 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN): ) -class LastFmOptionsFlowHandler(OptionsFlow): +class LastFmOptionsFlowHandler(OptionsFlowWithReload): """LastFm Options flow handler.""" config_entry: LastFMConfigEntry diff --git a/homeassistant/components/met/__init__.py b/homeassistant/components/met/__init__.py index 17fc411bf20..d5f80d442a4 100644 --- a/homeassistant/components/met/__init__.py +++ b/homeassistant/components/met/__init__.py @@ -47,7 +47,6 @@ async def async_setup_entry( config_entry.runtime_data = coordinator - config_entry.async_on_unload(config_entry.add_update_listener(async_update_entry)) config_entry.async_on_unload(coordinator.untrack_home) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) @@ -64,11 +63,6 @@ async def async_unload_entry( return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) -async def async_update_entry(hass: HomeAssistant, config_entry: MetWeatherConfigEntry): - """Reload Met component when options changed.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - async def cleanup_old_device(hass: HomeAssistant) -> None: """Cleanup device without proper device identifier.""" device_reg = dr.async_get(hass) diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index e5db80b2997..54d528a7406 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -10,7 +10,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_ELEVATION, @@ -147,7 +147,7 @@ class MetConfigFlowHandler(ConfigFlow, domain=DOMAIN): return MetOptionsFlowHandler() -class MetOptionsFlowHandler(OptionsFlow): +class MetOptionsFlowHandler(OptionsFlowWithReload): """Options flow for Met component.""" async def async_step_init( diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 2abcc273e23..9c4d1a97f00 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -120,8 +120,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) - return True @@ -145,8 +143,3 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> multicast.Stop_listen() return unload_ok - - -async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/motion_blinds/config_flow.py b/homeassistant/components/motion_blinds/config_flow.py index 954f9e25c21..8323c0e1995 100644 --- a/homeassistant/components/motion_blinds/config_flow.py +++ b/homeassistant/components/motion_blinds/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_API_KEY, CONF_HOST from homeassistant.core import callback @@ -38,7 +38,7 @@ CONFIG_SCHEMA = vol.Schema( ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Options for the component.""" async def async_step_init( diff --git a/homeassistant/components/motioneye/__init__.py b/homeassistant/components/motioneye/__init__.py index 3e4ad53d200..fec176847da 100644 --- a/homeassistant/components/motioneye/__init__.py +++ b/homeassistant/components/motioneye/__init__.py @@ -277,11 +277,6 @@ def _add_camera( ) -async def _async_entry_updated(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - """Handle entry updates.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up motionEye from a config entry.""" hass.data.setdefault(DOMAIN, {}) @@ -382,7 +377,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator.async_add_listener(_async_process_motioneye_cameras) ) await coordinator.async_refresh() - entry.async_on_unload(entry.add_update_listener(_async_entry_updated)) return True diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py index 80a6449a22d..7704fb68412 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -17,7 +17,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_URL, CONF_WEBHOOK_ID from homeassistant.core import callback @@ -186,7 +186,7 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN): return MotionEyeOptionsFlow() -class MotionEyeOptionsFlow(OptionsFlow): +class MotionEyeOptionsFlow(OptionsFlowWithReload): """motionEye options flow.""" async def async_step_init( diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 1315463ebcf..8cb66270331 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -678,7 +678,7 @@ }, "data_description": { "discovery_topic": "The [discovery topic]({url}#discovery-topic) to publish the discovery payload, used to trigger MQTT discovery. An empty payload published to this topic will remove the device and discovered entities.", - "discovery_payload": "The JSON [discovery payload]({url}#discovery-discovery-payload) that contains information about the MQTT device." + "discovery_payload": "The JSON [discovery payload]({url}#device-discovery-payload) that contains information about the MQTT device." } } }, diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index fa18c3510ba..9aafa482faf 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -61,8 +61,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) - entry.async_on_unload(entry.add_update_listener(update_listener)) - async def async_update_devices() -> bool: """Fetch data from the router.""" if router.track_devices: @@ -194,11 +192,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - async def async_remove_config_entry_device( hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry ) -> bool: diff --git a/homeassistant/components/netgear/config_flow.py b/homeassistant/components/netgear/config_flow.py index a0a5b76eee5..3386d07cc6d 100644 --- a/homeassistant/components/netgear/config_flow.py +++ b/homeassistant/components/netgear/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_HOST, @@ -65,7 +65,7 @@ def _ordered_shared_schema(schema_input): } -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Options for the component.""" async def async_step_init( diff --git a/homeassistant/components/nina/__init__.py b/homeassistant/components/nina/__init__.py index e074f7ad000..f9b23faa234 100644 --- a/homeassistant/components/nina/__init__.py +++ b/homeassistant/components/nina/__init__.py @@ -37,8 +37,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: NinaConfigEntry) -> bool await coordinator.async_config_entry_first_refresh() - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) - entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -49,8 +47,3 @@ async def async_setup_entry(hass: HomeAssistant, entry: NinaConfigEntry) -> bool async def async_unload_entry(hass: HomeAssistant, entry: NinaConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def _async_update_listener(hass: HomeAssistant, entry: NinaConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/nina/config_flow.py b/homeassistant/components/nina/config_flow.py index 24c016e5e64..f7bc0914481 100644 --- a/homeassistant/components/nina/config_flow.py +++ b/homeassistant/components/nina/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entity_registry as er @@ -165,8 +165,8 @@ class NinaConfigFlow(ConfigFlow, domain=DOMAIN): return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(OptionsFlow): - """Handle a option flow for nut.""" +class OptionsFlowHandler(OptionsFlowWithReload): + """Handle an option flow for NINA.""" def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" diff --git a/homeassistant/components/nmap_tracker/__init__.py b/homeassistant/components/nmap_tracker/__init__.py index 72bf9284573..2aa77e09d16 100644 --- a/homeassistant/components/nmap_tracker/__init__.py +++ b/homeassistant/components/nmap_tracker/__init__.py @@ -88,16 +88,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: devices = domain_data.setdefault(NMAP_TRACKED_DEVICES, NmapTrackedDevices()) scanner = domain_data[entry.entry_id] = NmapDeviceScanner(hass, entry, devices) await scanner.async_setup() - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/nmap_tracker/config_flow.py b/homeassistant/components/nmap_tracker/config_flow.py index 1f436edd60c..e3d1ecbdb14 100644 --- a/homeassistant/components/nmap_tracker/config_flow.py +++ b/homeassistant/components/nmap_tracker/config_flow.py @@ -18,7 +18,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_EXCLUDE, CONF_HOSTS from homeassistant.core import HomeAssistant, callback @@ -138,7 +138,7 @@ async def _async_build_schema_with_user_input( return vol.Schema(schema) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow for homekit.""" def __init__(self, config_entry: ConfigEntry) -> None: diff --git a/homeassistant/components/nobo_hub/__init__.py b/homeassistant/components/nobo_hub/__init__.py index 3bbf46f0264..7c886c534cb 100644 --- a/homeassistant/components/nobo_hub/__init__.py +++ b/homeassistant/components/nobo_hub/__init__.py @@ -42,8 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(options_update_listener)) - await hub.start() return True @@ -58,10 +56,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -async def options_update_listener( - hass: HomeAssistant, config_entry: ConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/nobo_hub/config_flow.py b/homeassistant/components/nobo_hub/config_flow.py index 7e1ae4c1d9b..05ece456f15 100644 --- a/homeassistant/components/nobo_hub/config_flow.py +++ b/homeassistant/components/nobo_hub/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import callback @@ -173,7 +173,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: ConfigEntry, - ) -> OptionsFlow: + ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler() @@ -187,7 +187,7 @@ class NoboHubConnectError(HomeAssistantError): self.msg = msg -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handles options flow for the component.""" async def async_step_init(self, user_input=None) -> ConfigFlowResult: diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 2f2c6badc4c..e3460f5a687 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -116,7 +116,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: NutConfigEntry) -> bool: _LOGGER.debug("NUT Sensors Available: %s", status if status else None) - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) unique_id = _unique_id_from_status(status) if unique_id is None: unique_id = entry.entry_id @@ -199,11 +198,6 @@ async def async_remove_config_entry_device( ) -async def _async_update_listener(hass: HomeAssistant, entry: NutConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - def _manufacturer_from_status(status: dict[str, str]) -> str | None: """Find the best manufacturer value from the status.""" return ( diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index c77d87d91b9..396539d93e3 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -39,8 +39,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: OneWireConfigEntry) -> b onewire_hub.schedule_scan_for_new_devices() - entry.async_on_unload(entry.add_update_listener(options_update_listener)) - return True @@ -59,11 +57,3 @@ async def async_unload_entry( ) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(config_entry, _PLATFORMS) - - -async def options_update_listener( - hass: HomeAssistant, entry: OneWireConfigEntry -) -> None: - """Handle options update.""" - _LOGGER.debug("Configuration options updated, reloading OneWire integration") - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 2099d9aabb5..0f2a2b6c51c 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -8,7 +8,11 @@ from typing import Any from pyownet import protocol import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr @@ -160,7 +164,7 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): return OnewireOptionsFlowHandler(config_entry) -class OnewireOptionsFlowHandler(OptionsFlow): +class OnewireOptionsFlowHandler(OptionsFlowWithReload): """Handle OneWire Config options.""" configurable_devices: dict[str, str] diff --git a/homeassistant/components/onkyo/__init__.py b/homeassistant/components/onkyo/__init__.py index 67ed4162778..d0f93012eb7 100644 --- a/homeassistant/components/onkyo/__init__.py +++ b/homeassistant/components/onkyo/__init__.py @@ -47,7 +47,6 @@ async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: OnkyoConfigEntry) -> bool: """Set up the Onkyo config entry.""" - entry.async_on_unload(entry.add_update_listener(update_listener)) host = entry.data[CONF_HOST] @@ -82,8 +81,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: OnkyoConfigEntry) -> bo receiver.conn.close() return unload_ok - - -async def update_listener(hass: HomeAssistant, entry: OnkyoConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/onkyo/config_flow.py b/homeassistant/components/onkyo/config_flow.py index 85ff0de3251..2b8f9981e4a 100644 --- a/homeassistant/components/onkyo/config_flow.py +++ b/homeassistant/components/onkyo/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST from homeassistant.core import callback @@ -329,7 +329,7 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowWithReload: """Return the options flow.""" return OnkyoOptionsFlowHandler() @@ -357,7 +357,7 @@ OPTIONS_STEP_INIT_SCHEMA = vol.Schema( ) -class OnkyoOptionsFlowHandler(OptionsFlow): +class OnkyoOptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow for Onkyo.""" _data: dict[str, Any] diff --git a/homeassistant/components/ping/__init__.py b/homeassistant/components/ping/__init__.py index 14203541359..f1d0113ac5e 100644 --- a/homeassistant/components/ping/__init__.py +++ b/homeassistant/components/ping/__init__.py @@ -50,16 +50,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: PingConfigEntry) -> bool entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True -async def async_reload_entry(hass: HomeAssistant, entry: PingConfigEntry) -> None: - """Handle an options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: PingConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/ping/config_flow.py b/homeassistant/components/ping/config_flow.py index 27cb3f62bcd..d66f4beb8e5 100644 --- a/homeassistant/components/ping/config_flow.py +++ b/homeassistant/components/ping/config_flow.py @@ -15,7 +15,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST from homeassistant.core import callback @@ -71,12 +71,12 @@ class PingConfigFlow(ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: ConfigEntry, - ) -> OptionsFlow: + ) -> OptionsFlowHandler: """Create the options flow.""" return OptionsFlowHandler() -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow for Ping.""" async def async_step_init( diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 2338464558d..4dc87554055 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -43,17 +43,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ProximityConfigEntry) -> entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR]) - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) return True async def async_unload_entry(hass: HomeAssistant, entry: ProximityConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, [Platform.SENSOR]) - - -async def _async_update_listener( - hass: HomeAssistant, entry: ProximityConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/proximity/config_flow.py b/homeassistant/components/proximity/config_flow.py index 5818ec2979b..f60dcfae7b5 100644 --- a/homeassistant/components/proximity/config_flow.py +++ b/homeassistant/components/proximity/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_ZONE, UnitOfLength from homeassistant.core import State, callback @@ -87,7 +87,7 @@ class ProximityConfigFlow(ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + def async_get_options_flow(config_entry: ConfigEntry) -> ProximityOptionsFlow: """Get the options flow for this handler.""" return ProximityOptionsFlow() @@ -118,7 +118,7 @@ class ProximityConfigFlow(ConfigFlow, domain=DOMAIN): ) -class ProximityOptionsFlow(OptionsFlow): +class ProximityOptionsFlow(OptionsFlowWithReload): """Handle a option flow.""" def _user_form_schema(self, user_input: dict[str, Any]) -> vol.Schema: diff --git a/homeassistant/components/purpleair/__init__.py b/homeassistant/components/purpleair/__init__.py index 78986b34351..0b7acdb1eb0 100644 --- a/homeassistant/components/purpleair/__init__.py +++ b/homeassistant/components/purpleair/__init__.py @@ -20,16 +20,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: PurpleAirConfigEntry) -> await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(async_reload_entry)) - return True -async def async_reload_entry(hass: HomeAssistant, entry: PurpleAirConfigEntry) -> None: - """Reload config entry.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: PurpleAirConfigEntry) -> bool: """Unload config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/purpleair/config_flow.py b/homeassistant/components/purpleair/config_flow.py index 3ca7870b3cb..29139872913 100644 --- a/homeassistant/components/purpleair/config_flow.py +++ b/homeassistant/components/purpleair/config_flow.py @@ -17,7 +17,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_API_KEY, @@ -312,7 +312,7 @@ class PurpleAirConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_by_coordinates() -class PurpleAirOptionsFlowHandler(OptionsFlow): +class PurpleAirOptionsFlowHandler(OptionsFlowWithReload): """Handle a PurpleAir options flow.""" def __init__(self) -> None: diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index f9cd751a81e..e986cc302ae 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -218,6 +218,9 @@ def _async_fix_device_id( for device_entry in device_entries: unique_id = str(next(iter(device_entry.identifiers))[1]) device_entry_map[unique_id] = device_entry + if unique_id.startswith(mac_address): + # Already in the correct format + continue if (suffix := unique_id.removeprefix(str(serial_number))) != unique_id: migrations[unique_id] = f"{mac_address}{suffix}" diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 3260bff44b5..236e1707461 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -243,10 +243,6 @@ async def async_setup_entry( await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - config_entry.async_on_unload( - config_entry.add_update_listener(entry_update_listener) - ) - return True @@ -295,13 +291,6 @@ async def register_callbacks( ) -async def entry_update_listener( - hass: HomeAssistant, config_entry: ReolinkConfigEntry -) -> None: - """Update the configuration of the host entity.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - async def async_unload_entry( hass: HomeAssistant, config_entry: ReolinkConfigEntry ) -> bool: diff --git a/homeassistant/components/reolink/config_flow.py b/homeassistant/components/reolink/config_flow.py index eee8b04dfcc..2ac51792c3f 100644 --- a/homeassistant/components/reolink/config_flow.py +++ b/homeassistant/components/reolink/config_flow.py @@ -23,7 +23,7 @@ from homeassistant.config_entries import ( SOURCE_RECONFIGURE, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_HOST, @@ -61,7 +61,7 @@ DEFAULT_OPTIONS = {CONF_PROTOCOL: DEFAULT_PROTOCOL} API_STARTUP_TIME = 5 -class ReolinkOptionsFlowHandler(OptionsFlow): +class ReolinkOptionsFlowHandler(OptionsFlowWithReload): """Handle Reolink options.""" async def async_step_init( diff --git a/homeassistant/components/reolink/icons.json b/homeassistant/components/reolink/icons.json index cf3079e51e8..875af48e47c 100644 --- a/homeassistant/components/reolink/icons.json +++ b/homeassistant/components/reolink/icons.json @@ -402,7 +402,12 @@ "default": "mdi:thermometer" }, "battery_state": { - "default": "mdi:battery-charging" + "default": "mdi:battery-unknown", + "state": { + "discharging": "mdi:battery-minus-variant", + "charging": "mdi:battery-charging", + "chargecomplete": "mdi:battery-check" + } }, "day_night_state": { "default": "mdi:theme-light-dark" diff --git a/homeassistant/components/roborock/__init__.py b/homeassistant/components/roborock/__init__.py index 6697779adf6..bc10ab7309c 100644 --- a/homeassistant/components/roborock/__init__.py +++ b/homeassistant/components/roborock/__init__.py @@ -43,8 +43,6 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) -> bool: """Set up roborock from a config entry.""" - entry.async_on_unload(entry.add_update_listener(update_listener)) - user_data = UserData.from_dict(entry.data[CONF_USER_DATA]) api_client = RoborockApiClient( entry.data[CONF_USERNAME], @@ -336,12 +334,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: RoborockConfigEntry) -> return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) -async def update_listener(hass: HomeAssistant, entry: RoborockConfigEntry) -> None: - """Handle options update.""" - # Reload entry to update data - await hass.config_entries.async_reload(entry.entry_id) - - async def async_remove_entry(hass: HomeAssistant, entry: RoborockConfigEntry) -> None: """Handle removal of an entry.""" await async_remove_map_storage(hass, entry.entry_id) diff --git a/homeassistant/components/roborock/config_flow.py b/homeassistant/components/roborock/config_flow.py index 62943e0dcc9..6a35bf79233 100644 --- a/homeassistant/components/roborock/config_flow.py +++ b/homeassistant/components/roborock/config_flow.py @@ -23,7 +23,7 @@ from homeassistant.config_entries import ( SOURCE_REAUTH, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_USERNAME from homeassistant.core import callback @@ -124,14 +124,9 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN): if self.source == SOURCE_REAUTH: self._abort_if_unique_id_mismatch(reason="wrong_account") reauth_entry = self._get_reauth_entry() - self.hass.config_entries.async_update_entry( - reauth_entry, - data={ - **reauth_entry.data, - CONF_USER_DATA: user_data.as_dict(), - }, + return self.async_update_reload_and_abort( + reauth_entry, data_updates={CONF_USER_DATA: user_data.as_dict()} ) - return self.async_abort(reason="reauth_successful") self._abort_if_unique_id_configured(error="already_configured_account") return self._create_entry(self._client, self._username, user_data) @@ -202,7 +197,7 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN): return RoborockOptionsFlowHandler(config_entry) -class RoborockOptionsFlowHandler(OptionsFlow): +class RoborockOptionsFlowHandler(OptionsFlowWithReload): """Handle an option flow for Roborock.""" def __init__(self, config_entry: RoborockConfigEntry) -> None: diff --git a/homeassistant/components/schlage/manifest.json b/homeassistant/components/schlage/manifest.json index 893c30dfd41..c5b91cefd2e 100644 --- a/homeassistant/components/schlage/manifest.json +++ b/homeassistant/components/schlage/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/schlage", "iot_class": "cloud_polling", - "requirements": ["pyschlage==2025.4.0"] + "requirements": ["pyschlage==2025.7.2"] } diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index 89796f5ce46..fdbaaf9f427 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -11,8 +11,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from .const import CONF_SYSTEM_ID, DATA_SOMFY_MYLINK, DOMAIN, MYLINK_STATUS, PLATFORMS -UNDO_UPDATE_LISTENER = "undo_update_listener" - _LOGGER = logging.getLogger(__name__) @@ -44,12 +42,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if "result" not in mylink_status: raise ConfigEntryNotReady("The Somfy MyLink device returned an empty result") - undo_listener = entry.add_update_listener(_async_update_listener) - hass.data[DOMAIN][entry.entry_id] = { DATA_SOMFY_MYLINK: somfy_mylink, MYLINK_STATUS: mylink_status, - UNDO_UPDATE_LISTENER: undo_listener, } await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -57,18 +52,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() - - if unload_ok: + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index a806d581aec..91cfae87347 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ( ConfigEntryState, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback @@ -125,7 +125,7 @@ class SomfyConfigFlow(ConfigFlow, domain=DOMAIN): return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow for somfy_mylink.""" def __init__(self, config_entry: ConfigEntry) -> None: diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index e4f439013c6..5f66ba380fe 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -42,7 +42,6 @@ async def async_setup_entry( async_at_started(hass, _async_finish_startup) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) return True @@ -52,10 +51,3 @@ async def async_unload_entry( ) -> bool: """Unload SpeedTest Entry from config_entry.""" return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) - - -async def update_listener( - hass: HomeAssistant, config_entry: SpeedTestConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 4fbca5e0d29..4bae503f85e 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -6,7 +6,11 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.core import callback from .const import ( @@ -45,7 +49,7 @@ class SpeedTestFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=DEFAULT_NAME, data=user_input) -class SpeedTestOptionsFlowHandler(OptionsFlow): +class SpeedTestOptionsFlowHandler(OptionsFlowWithReload): """Handle SpeedTest options.""" def __init__(self) -> None: diff --git a/homeassistant/components/sql/__init__.py b/homeassistant/components/sql/__init__.py index e3e6c699d03..33ed64be2bf 100644 --- a/homeassistant/components/sql/__init__.py +++ b/homeassistant/components/sql/__init__.py @@ -87,11 +87,6 @@ def remove_configured_db_url_if_not_needed( ) -async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update listener for options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up SQL from yaml config.""" if (conf := config.get(DOMAIN)) is None: @@ -115,8 +110,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if entry.options.get(CONF_DB_URL) == get_instance(hass).db_url: remove_configured_db_url_if_not_needed(hass, entry) - entry.async_on_unload(entry.add_update_listener(async_update_listener)) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/sql/config_flow.py b/homeassistant/components/sql/config_flow.py index 4fe04f2401c..37a6f9ef104 100644 --- a/homeassistant/components/sql/config_flow.py +++ b/homeassistant/components/sql/config_flow.py @@ -23,7 +23,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_DEVICE_CLASS, @@ -209,7 +209,7 @@ class SQLConfigFlow(ConfigFlow, domain=DOMAIN): ) -class SQLOptionsFlowHandler(OptionsFlow): +class SQLOptionsFlowHandler(OptionsFlowWithReload): """Handle SQL options.""" async def async_step_init( diff --git a/homeassistant/components/squeezebox/__init__.py b/homeassistant/components/squeezebox/__init__.py index c6cb04b5ffb..2bd845923fc 100644 --- a/homeassistant/components/squeezebox/__init__.py +++ b/homeassistant/components/squeezebox/__init__.py @@ -112,9 +112,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) - if not status: # pysqueezebox's async_query returns None on various issues, # including HTTP errors where it sets lms.http_status. - http_status = getattr(lms, "http_status", "N/A") - if http_status == HTTPStatus.UNAUTHORIZED: + if lms.http_status == HTTPStatus.UNAUTHORIZED: _LOGGER.warning("Authentication failed for Squeezebox server %s", host) raise ConfigEntryAuthFailed( translation_domain=DOMAIN, @@ -128,14 +127,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) - _LOGGER.warning( "LMS %s returned no status or an error (HTTP status: %s). Retrying setup", host, - http_status, + lms.http_status, ) raise ConfigEntryNotReady( translation_domain=DOMAIN, translation_key="init_get_status_failed", translation_placeholders={ "host": str(host), - "http_status": str(http_status), + "http_status": str(lms.http_status), }, ) diff --git a/homeassistant/components/squeezebox/binary_sensor.py b/homeassistant/components/squeezebox/binary_sensor.py index 1045e526ee3..ea305d71f99 100644 --- a/homeassistant/components/squeezebox/binary_sensor.py +++ b/homeassistant/components/squeezebox/binary_sensor.py @@ -49,7 +49,7 @@ async def async_setup_entry( class ServerStatusBinarySensor(LMSStatusEntity, BinarySensorEntity): - """LMS Status based sensor from LMS via cooridnatior.""" + """LMS Status based sensor from LMS via coordinator.""" @property def is_on(self) -> bool: diff --git a/homeassistant/components/squeezebox/coordinator.py b/homeassistant/components/squeezebox/coordinator.py index 6582f143e79..9508420ec5f 100644 --- a/homeassistant/components/squeezebox/coordinator.py +++ b/homeassistant/components/squeezebox/coordinator.py @@ -30,7 +30,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -class LMSStatusDataUpdateCoordinator(DataUpdateCoordinator): +class LMSStatusDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """LMS Status custom coordinator.""" config_entry: SqueezeboxConfigEntry @@ -59,13 +59,13 @@ class LMSStatusDataUpdateCoordinator(DataUpdateCoordinator): else: _LOGGER.warning("Can't query server capabilities %s", self.lms.name) - async def _async_update_data(self) -> dict: + async def _async_update_data(self) -> dict[str, Any]: """Fetch data from LMS status call. Then we process only a subset to make then nice for HA """ async with timeout(STATUS_API_TIMEOUT): - data: dict | None = await self.lms.async_prepared_status() + data: dict[str, Any] | None = await self.lms.async_prepared_status() if not data: raise UpdateFailed( @@ -111,7 +111,7 @@ class SqueezeBoxPlayerUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Only update players available at last update, unavailable players are rediscovered instead await self.player.async_update() - if self.player.connected is False: + if not self.player.connected: _LOGGER.info("Player %s is not available", self.name) self.available = False diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index dc426d76588..0dbc1b96b0c 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -226,10 +226,7 @@ def get_announce_timeout(extra: dict) -> int | None: class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity): - """Representation of the media player features of a SqueezeBox device. - - Wraps a pysqueezebox.Player() object. - """ + """Representation of the media player features of a SqueezeBox device.""" _attr_supported_features = ( MediaPlayerEntityFeature.BROWSE_MEDIA @@ -286,9 +283,11 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity): @property def browse_limit(self) -> int: - """Return the step to be used for volume up down.""" - return self.coordinator.config_entry.options.get( # type: ignore[no-any-return] - CONF_BROWSE_LIMIT, DEFAULT_BROWSE_LIMIT + """Return the max number of items to return from browse.""" + return int( + self.coordinator.config_entry.options.get( + CONF_BROWSE_LIMIT, DEFAULT_BROWSE_LIMIT + ) ) @property diff --git a/homeassistant/components/squeezebox/sensor.py b/homeassistant/components/squeezebox/sensor.py index 11c169910dc..79390910ef7 100644 --- a/homeassistant/components/squeezebox/sensor.py +++ b/homeassistant/components/squeezebox/sensor.py @@ -88,7 +88,7 @@ async def async_setup_entry( class ServerStatusSensor(LMSStatusEntity, SensorEntity): - """LMS Status based sensor from LMS via cooridnatior.""" + """LMS Status based sensor from LMS via coordinator.""" @property def native_value(self) -> StateType: diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index e568ce5a6d1..7146d42136e 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -136,7 +136,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: SynologyDSMConfigEntry) coordinator_switches=coordinator_switches, ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) if entry.options[CONF_BACKUP_SHARE]: @@ -172,13 +171,6 @@ async def async_unload_entry( return unload_ok -async def _async_update_listener( - hass: HomeAssistant, entry: SynologyDSMConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_remove_config_entry_device( hass: HomeAssistant, entry: SynologyDSMConfigEntry, device_entry: dr.DeviceEntry ) -> bool: diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index f0da6f8fe47..6e3469970d1 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -24,7 +24,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_DISKS, @@ -441,7 +441,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): return None -class SynologyDSMOptionsFlowHandler(OptionsFlow): +class SynologyDSMOptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow.""" config_entry: SynologyDSMConfigEntry diff --git a/homeassistant/components/tankerkoenig/__init__.py b/homeassistant/components/tankerkoenig/__init__.py index b2b60db9675..2a85b1f31e1 100644 --- a/homeassistant/components/tankerkoenig/__init__.py +++ b/homeassistant/components/tankerkoenig/__init__.py @@ -23,8 +23,6 @@ async def async_setup_entry( entry.runtime_data = coordinator - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -35,10 +33,3 @@ async def async_unload_entry( ) -> bool: """Unload Tankerkoenig config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def _async_update_listener( - hass: HomeAssistant, entry: TankerkoenigConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/tankerkoenig/config_flow.py b/homeassistant/components/tankerkoenig/config_flow.py index b269eaaaf55..9aeb0a80173 100644 --- a/homeassistant/components/tankerkoenig/config_flow.py +++ b/homeassistant/components/tankerkoenig/config_flow.py @@ -18,7 +18,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_API_KEY, @@ -229,7 +229,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle an options flow.""" def __init__(self) -> None: diff --git a/homeassistant/components/trafikverket_train/__init__.py b/homeassistant/components/trafikverket_train/__init__.py index 19f88817e71..7cdb0c02f5b 100644 --- a/homeassistant/components/trafikverket_train/__init__.py +++ b/homeassistant/components/trafikverket_train/__init__.py @@ -42,7 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TVTrainConfigEntry) -> b ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -53,11 +52,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: TVTrainConfigEntry) -> return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) -async def update_listener(hass: HomeAssistant, entry: TVTrainConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_migrate_entry(hass: HomeAssistant, entry: TVTrainConfigEntry) -> bool: """Migrate config entry.""" _LOGGER.debug("Migrating from version %s", entry.version) diff --git a/homeassistant/components/trafikverket_train/config_flow.py b/homeassistant/components/trafikverket_train/config_flow.py index fb39e14815e..2328a7126fd 100644 --- a/homeassistant/components/trafikverket_train/config_flow.py +++ b/homeassistant/components/trafikverket_train/config_flow.py @@ -20,7 +20,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS from homeassistant.core import HomeAssistant, callback @@ -329,7 +329,7 @@ class TVTrainConfigFlow(ConfigFlow, domain=DOMAIN): ) -class TVTrainOptionsFlowHandler(OptionsFlow): +class TVTrainOptionsFlowHandler(OptionsFlowWithReload): """Handle Trafikverket Train options.""" async def async_step_init( diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 2d75010b4e5..440250d45a3 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -114,7 +114,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool: hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac) entry.runtime_data = data_service - entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop) ) @@ -139,11 +138,6 @@ async def _async_setup_entry( hass.http.register_view(VideoEventProxyView(hass)) -async def _async_options_updated(hass: HomeAssistant, entry: UFPConfigEntry) -> None: - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool: """Unload UniFi Protect config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 9f7f4bccd7f..c83b3f11010 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -20,7 +20,7 @@ from homeassistant.config_entries import ( ConfigEntryState, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import ( CONF_HOST, @@ -223,7 +223,7 @@ class ProtectFlowHandler(ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: ConfigEntry, - ) -> OptionsFlow: + ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler() @@ -372,7 +372,7 @@ class ProtectFlowHandler(ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle options.""" async def async_step_init( diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 8243a55d779..e5b017e0ab6 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -40,7 +40,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["uiprotect", "unifi_discovery"], - "requirements": ["uiprotect==7.14.2", "unifi-discovery==1.2.0"], + "requirements": ["uiprotect==7.16.0", "unifi-discovery==1.2.0"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index b8f0b702ebe..aedc174cb6d 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -143,7 +143,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription) ) - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) return True @@ -161,11 +160,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - def map_vera_device( vera_device: veraApi.VeraDevice, remap: list[int] ) -> Platform | None: diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index f2b182cc270..f02549e7857 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -17,7 +17,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.core import callback @@ -73,7 +73,7 @@ def options_data(user_input: dict[str, str]) -> dict[str, list[int]]: ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Options for the component.""" async def async_step_init( diff --git a/homeassistant/components/vodafone_station/__init__.py b/homeassistant/components/vodafone_station/__init__.py index 17b0fe6e501..0433199b54e 100644 --- a/homeassistant/components/vodafone_station/__init__.py +++ b/homeassistant/components/vodafone_station/__init__.py @@ -25,8 +25,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) -> entry.runtime_data = coordinator - entry.async_on_unload(entry.add_update_listener(update_listener)) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -39,9 +37,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) -> await coordinator.api.logout() return unload_ok - - -async def update_listener(hass: HomeAssistant, entry: VodafoneConfigEntry) -> None: - """Update when config_entry options update.""" - if entry.options: - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/vodafone_station/config_flow.py b/homeassistant/components/vodafone_station/config_flow.py index c330a93a1a8..13e30d38926 100644 --- a/homeassistant/components/vodafone_station/config_flow.py +++ b/homeassistant/components/vodafone_station/config_flow.py @@ -12,7 +12,11 @@ from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, ) -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback @@ -180,7 +184,7 @@ class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN): ) -class VodafoneStationOptionsFlowHandler(OptionsFlow): +class VodafoneStationOptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow.""" async def async_step_init( diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index c1a1c698f92..fb729707154 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -75,8 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> b ) ) - entry.async_on_unload(entry.add_update_listener(async_update_options)) - async def async_on_stop(_event: Event) -> None: """Unregister callbacks and disconnect.""" client.clear_state_update_callbacks() @@ -88,11 +86,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> b return True -async def async_update_options(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> None: - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> bool: """Unload a config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 2af38cb3d17..44711c2b456 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -9,7 +9,11 @@ from urllib.parse import urlparse from aiowebostv import WebOsClient, WebOsTvPairError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv @@ -60,7 +64,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry: WebOsTvConfigEntry) -> OptionsFlow: + def async_get_options_flow(config_entry: WebOsTvConfigEntry) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @@ -197,7 +201,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle options.""" def __init__(self, config_entry: WebOsTvConfigEntry) -> None: diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 6cf216011f2..b6811190a27 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -29,8 +29,6 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up wiffi from a config entry, config_entry contains data from config entry database.""" - if not entry.update_listeners: - entry.add_update_listener(async_update_options) # create api object api = WiffiIntegrationApi(hass) @@ -53,11 +51,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" api: WiffiIntegrationApi = hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/wiffi/config_flow.py b/homeassistant/components/wiffi/config_flow.py index 308923597cd..c40bd5519e0 100644 --- a/homeassistant/components/wiffi/config_flow.py +++ b/homeassistant/components/wiffi/config_flow.py @@ -15,7 +15,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import callback @@ -76,7 +76,7 @@ class WiffiFlowHandler(ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Wiffi server setup option flow.""" async def async_step_init( diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index b4834347694..c3917507fb9 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -48,9 +48,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: WLEDConfigEntry) -> bool # Set up all platforms for this device/entry. await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - # Reload entry when its updated. - entry.async_on_unload(entry.add_update_listener(async_reload_entry)) - return True @@ -65,8 +62,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: WLEDConfigEntry) -> boo coordinator.unsub() return unload_ok - - -async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Reload the config entry when it changed.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index 2e0b7b1c793..e80760508a0 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import callback @@ -120,7 +120,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): return await wled.update() -class WLEDOptionsFlowHandler(OptionsFlow): +class WLEDOptionsFlowHandler(OptionsFlowWithReload): """Handle WLED options.""" async def async_step_init( diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py index 60a0489ec5c..0df4224a4ca 100644 --- a/homeassistant/components/workday/__init__.py +++ b/homeassistant/components/workday/__init__.py @@ -94,16 +94,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: new_options[CONF_LANGUAGE] = default_language hass.config_entries.async_update_entry(entry, options=new_options) - entry.async_on_unload(entry.add_update_listener(async_update_listener)) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update listener for options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Workday config entry.""" diff --git a/homeassistant/components/workday/config_flow.py b/homeassistant/components/workday/config_flow.py index 7a8a8181a9f..1d91e1d5ae3 100644 --- a/homeassistant/components/workday/config_flow.py +++ b/homeassistant/components/workday/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_COUNTRY, CONF_LANGUAGE, CONF_NAME from homeassistant.core import callback @@ -311,7 +311,7 @@ class WorkdayConfigFlow(ConfigFlow, domain=DOMAIN): ) -class WorkdayOptionsFlowHandler(OptionsFlow): +class WorkdayOptionsFlowHandler(OptionsFlowWithReload): """Handle Workday options.""" async def async_step_init( diff --git a/homeassistant/components/ws66i/__init__.py b/homeassistant/components/ws66i/__init__.py index 32c6a11f25c..23a27adeb69 100644 --- a/homeassistant/components/ws66i/__init__.py +++ b/homeassistant/components/ws66i/__init__.py @@ -100,7 +100,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Close the WS66i connection to the amplifier.""" ws66i.close() - entry.async_on_unload(entry.add_update_listener(_update_listener)) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) ) @@ -119,8 +118,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/ws66i/config_flow.py b/homeassistant/components/ws66i/config_flow.py index 120b7738d2e..e70dbd4e8d7 100644 --- a/homeassistant/components/ws66i/config_flow.py +++ b/homeassistant/components/ws66i/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant, callback @@ -142,7 +142,7 @@ def _key_for_source( ) -class Ws66iOptionsFlowHandler(OptionsFlow): +class Ws66iOptionsFlowHandler(OptionsFlowWithReload): """Handle a WS66i options flow.""" async def async_step_init( diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 0e28a2900bb..8db5273174b 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -466,8 +466,6 @@ async def async_setup_gateway_entry( await hass.config_entries.async_forward_entry_setups(entry, GATEWAY_PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) - async def async_setup_device_entry( hass: HomeAssistant, entry: XiaomiMiioConfigEntry @@ -481,8 +479,6 @@ async def async_setup_device_entry( await hass.config_entries.async_forward_entry_setups(entry, platforms) - entry.async_on_unload(entry.add_update_listener(update_listener)) - return True @@ -493,10 +489,3 @@ async def async_unload_entry( platforms = get_platforms(config_entry) return await hass.config_entries.async_unload_platforms(config_entry, platforms) - - -async def update_listener( - hass: HomeAssistant, config_entry: XiaomiMiioConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index b8d8b028006..95eabb0188c 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -11,7 +11,11 @@ from micloud import MiCloud from micloud.micloudexception import MiCloudAccessDenied import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow +from homeassistant.config_entries import ( + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithReload, +) from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_MODEL, CONF_TOKEN from homeassistant.core import callback from homeassistant.helpers.device_registry import format_mac @@ -56,7 +60,7 @@ DEVICE_CLOUD_CONFIG = vol.Schema( ) -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Options for the component.""" async def async_step_init( diff --git a/homeassistant/components/yale_smart_alarm/__init__.py b/homeassistant/components/yale_smart_alarm/__init__.py index d67e136be4a..5c481719cc9 100644 --- a/homeassistant/components/yale_smart_alarm/__init__.py +++ b/homeassistant/components/yale_smart_alarm/__init__.py @@ -22,16 +22,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True -async def update_listener(hass: HomeAssistant, entry: YaleConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index 1aaad2aa63a..d8c1fc80f8f 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback @@ -171,7 +171,7 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): ) -class YaleOptionsFlowHandler(OptionsFlow): +class YaleOptionsFlowHandler(OptionsFlowWithReload): """Handle Yale options.""" async def async_step_init( diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index 0b3ceaf2aee..cb24edae1fd 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -232,9 +232,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - # Wait to install the reload listener until everything was successfully initialized - entry.async_on_unload(entry.add_update_listener(_async_update_listener)) - return True @@ -245,11 +242,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def _async_get_device( hass: HomeAssistant, host: str, entry: ConfigEntry ) -> YeelightDevice: diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 15975ba22bd..cc3ab35f684 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -17,7 +17,7 @@ from homeassistant.config_entries import ( ConfigEntryState, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithReload, ) from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME from homeassistant.core import callback @@ -298,7 +298,7 @@ class YeelightConfigFlow(ConfigFlow, domain=DOMAIN): return MODEL_UNKNOWN -class OptionsFlowHandler(OptionsFlow): +class OptionsFlowHandler(OptionsFlowWithReload): """Handle a option flow for Yeelight.""" async def async_step_init( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e76b7ae099f..1c4f2b51ac7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -3491,7 +3491,22 @@ class OptionsFlowManager( entry = self.hass.config_entries.async_get_known_entry(flow.handler) if result["data"] is not None: - self.hass.config_entries.async_update_entry(entry, options=result["data"]) + automatic_reload = False + if isinstance(flow, OptionsFlowWithReload): + automatic_reload = flow.automatic_reload + + if automatic_reload and entry.update_listeners: + raise ValueError( + "Config entry update listeners should not be used with OptionsFlowWithReload" + ) + + if ( + self.hass.config_entries.async_update_entry( + entry, options=result["data"] + ) + and automatic_reload is True + ): + self.hass.config_entries.async_schedule_reload(entry.entry_id) result["result"] = True return result @@ -3600,6 +3615,18 @@ class OptionsFlowWithConfigEntry(OptionsFlow): return self._options +class OptionsFlowWithReload(OptionsFlow): + """Automatic reloading class for config options flows. + + Triggers an automatic reload of the config entry when the flow ends with + calling `async_create_entry` with changed options. + It's not allowed to use this class if the integration uses config entry + update listeners. + """ + + automatic_reload: bool = True + + class EntityRegistryDisabledHandler: """Handler when entities related to config entries updated disabled_by.""" diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 480a88e1ae4..8782d5c84b4 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -665,6 +665,11 @@ "config_flow": true, "iot_class": "local_push" }, + "bauknecht": { + "name": "Bauknecht", + "integration_type": "virtual", + "supported_by": "whirlpool" + }, "bbox": { "name": "Bbox", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 03019fcc39e..b7e3fd074b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -247,7 +247,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==36.0.1 +aioesphomeapi==37.0.2 # homeassistant.components.flo aioflo==2021.11.0 @@ -1174,7 +1174,7 @@ home-assistant-frontend==20250702.3 home-assistant-intents==2025.6.23 # homeassistant.components.homematicip_cloud -homematicip==2.0.7 +homematicip==2.2.0 # homeassistant.components.horizon horimote==0.4.1 @@ -2310,7 +2310,7 @@ pysabnzbd==1.1.1 pysaj==0.0.16 # homeassistant.components.schlage -pyschlage==2025.4.0 +pyschlage==2025.7.2 # homeassistant.components.sensibo pysensibo==1.2.1 @@ -3001,7 +3001,7 @@ typedmonarchmoney==0.4.4 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==7.14.2 +uiprotect==7.16.0 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0042ef7aa34..30ad1b2e5fe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -235,7 +235,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==36.0.1 +aioesphomeapi==37.0.2 # homeassistant.components.flo aioflo==2021.11.0 @@ -1023,7 +1023,7 @@ home-assistant-frontend==20250702.3 home-assistant-intents==2025.6.23 # homeassistant.components.homematicip_cloud -homematicip==2.0.7 +homematicip==2.2.0 # homeassistant.components.remember_the_milk httplib2==0.20.4 @@ -1922,7 +1922,7 @@ pyrympro==0.0.9 pysabnzbd==1.1.1 # homeassistant.components.schlage -pyschlage==2025.4.0 +pyschlage==2025.7.2 # homeassistant.components.sensibo pysensibo==1.2.1 @@ -2475,7 +2475,7 @@ typedmonarchmoney==0.4.4 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==7.14.2 +uiprotect==7.16.0 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/script/hassfest/quality_scale.py b/script/hassfest/quality_scale.py index b5fd8c3ad7a..3008c6303ff 100644 --- a/script/hassfest/quality_scale.py +++ b/script/hassfest/quality_scale.py @@ -1162,7 +1162,6 @@ INTEGRATIONS_WITHOUT_SCALE = [ "aftership", "agent_dvr", "airly", - "airgradient", "airnow", "airq", "airthings", diff --git a/tests/components/gardena_bluetooth/snapshots/test_number.ambr b/tests/components/gardena_bluetooth/snapshots/test_number.ambr index c89ead450d2..4bc1e7e8dcb 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_number.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_number.ambr @@ -2,6 +2,7 @@ # name: test_bluetooth_error_unavailable StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -20,6 +21,7 @@ # name: test_bluetooth_error_unavailable.1 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Manual watering time', 'max': 86400, 'min': 0.0, @@ -38,6 +40,7 @@ # name: test_bluetooth_error_unavailable.2 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -56,6 +59,7 @@ # name: test_bluetooth_error_unavailable.3 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Manual watering time', 'max': 86400, 'min': 0.0, @@ -110,6 +114,7 @@ # name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time] StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -128,6 +133,7 @@ # name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time].1 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -146,6 +152,7 @@ # name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time].2 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -164,6 +171,7 @@ # name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time].3 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Remaining open time', 'max': 86400, 'min': 0.0, @@ -182,6 +190,7 @@ # name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw2-number.mock_title_open_for] StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Open for', 'max': 1440, 'min': 0.0, @@ -200,6 +209,7 @@ # name: test_setup[98bd0f14-0b0e-421a-84e5-ddbf75dc6de4-raw0-number.mock_title_manual_watering_time] StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Manual watering time', 'max': 86400, 'min': 0.0, @@ -218,6 +228,7 @@ # name: test_setup[98bd0f14-0b0e-421a-84e5-ddbf75dc6de4-raw0-number.mock_title_manual_watering_time].1 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'duration', 'friendly_name': 'Mock Title Manual watering time', 'max': 86400, 'min': 0.0, diff --git a/tests/components/gardena_bluetooth/snapshots/test_valve.ambr b/tests/components/gardena_bluetooth/snapshots/test_valve.ambr index c030332e75b..4a0da40a143 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_valve.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_valve.ambr @@ -2,6 +2,7 @@ # name: test_setup StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'water', 'friendly_name': 'Mock Title', 'supported_features': , }), @@ -16,6 +17,7 @@ # name: test_setup.1 StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'water', 'friendly_name': 'Mock Title', 'supported_features': , }), diff --git a/tests/components/huum/snapshots/test_binary_sensor.ambr b/tests/components/huum/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000..3490ff594b6 --- /dev/null +++ b/tests/components/huum/snapshots/test_binary_sensor.ambr @@ -0,0 +1,50 @@ +# serializer version: 1 +# name: test_binary_sensor[binary_sensor.huum_sauna_door-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.huum_sauna_door', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Door', + 'platform': 'huum', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'AABBCC112233_door', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensor[binary_sensor.huum_sauna_door-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Huum sauna Door', + }), + 'context': , + 'entity_id': 'binary_sensor.huum_sauna_door', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/huum/test_binary_sensor.py b/tests/components/huum/test_binary_sensor.py new file mode 100644 index 00000000000..5ea2ae69a11 --- /dev/null +++ b/tests/components/huum/test_binary_sensor.py @@ -0,0 +1,29 @@ +"""Tests for the Huum climate entity.""" + +from unittest.mock import AsyncMock + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_with_selected_platforms + +from tests.common import MockConfigEntry, snapshot_platform + +ENTITY_ID = "binary_sensor.huum_sauna_door" + + +async def test_binary_sensor( + hass: HomeAssistant, + mock_huum: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, +) -> None: + """Test the initial parameters.""" + await setup_with_selected_platforms( + hass, mock_config_entry, [Platform.BINARY_SENSOR] + ) + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py index 8d942e7a2a1..f3c4820ff90 100644 --- a/tests/components/motioneye/test_config_flow.py +++ b/tests/components/motioneye/test_config_flow.py @@ -532,7 +532,7 @@ async def test_advanced_options(hass: HomeAssistant) -> None: assert result["data"][CONF_WEBHOOK_SET_OVERWRITE] assert CONF_STREAM_URL_TEMPLATE not in result["data"] assert len(mock_setup.mock_calls) == 0 - assert len(mock_setup_entry.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 1 result = await hass.config_entries.options.async_init( config_entry.entry_id, context={"show_advanced_options": True} @@ -551,4 +551,4 @@ async def test_advanced_options(hass: HomeAssistant) -> None: assert result["data"][CONF_WEBHOOK_SET_OVERWRITE] assert result["data"][CONF_STREAM_URL_TEMPLATE] == "http://moo" assert len(mock_setup.mock_calls) == 0 - assert len(mock_setup_entry.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/motioneye/test_web_hooks.py b/tests/components/motioneye/test_web_hooks.py index bc345c0b66f..4e9d5e926a8 100644 --- a/tests/components/motioneye/test_web_hooks.py +++ b/tests/components/motioneye/test_web_hooks.py @@ -116,7 +116,6 @@ async def test_setup_camera_with_wrong_webhook( ) assert not client.async_set_camera.called - # Update the options, which will trigger a reload with the new behavior. with patch( "homeassistant.components.motioneye.MotionEyeClient", return_value=client, @@ -124,6 +123,7 @@ async def test_setup_camera_with_wrong_webhook( hass.config_entries.async_update_entry( config_entry, options={CONF_WEBHOOK_SET_OVERWRITE: True} ) + await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() device = device_registry.async_get_device( diff --git a/tests/components/nina/test_config_flow.py b/tests/components/nina/test_config_flow.py index 309c8860c20..06eb94d59d0 100644 --- a/tests/components/nina/test_config_flow.py +++ b/tests/components/nina/test_config_flow.py @@ -323,9 +323,6 @@ async def test_options_flow_entity_removal( "pynina.baseApi.BaseAPI._makeRequest", wraps=mocked_request_function, ), - patch( - "homeassistant.components.nina._async_update_listener" - ) as mock_update_listener, ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -352,4 +349,3 @@ async def test_options_flow_entity_removal( ) assert len(entries) == 2 - assert len(mock_update_listener.mock_calls) == 1 diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py index 0748481c40b..ace7afb5645 100644 --- a/tests/components/onewire/test_init.py +++ b/tests/components/onewire/test_init.py @@ -1,6 +1,5 @@ """Tests for 1-Wire config flow.""" -from copy import deepcopy from unittest.mock import MagicMock, patch from freezegun.api import FrozenDateTimeFactory @@ -63,27 +62,6 @@ async def test_unload_entry(hass: HomeAssistant, config_entry: MockConfigEntry) assert config_entry.state is ConfigEntryState.NOT_LOADED -async def test_update_options( - hass: HomeAssistant, config_entry: MockConfigEntry, owproxy: MagicMock -) -> None: - """Test update options triggers reload.""" - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert config_entry.state is ConfigEntryState.LOADED - assert owproxy.call_count == 1 - - new_options = deepcopy(dict(config_entry.options)) - new_options["device_options"].clear() - hass.config_entries.async_update_entry(config_entry, options=new_options) - await hass.async_block_till_done() - - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert config_entry.state is ConfigEntryState.LOADED - assert owproxy.call_count == 2 - - @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_registry( hass: HomeAssistant, diff --git a/tests/components/onkyo/test_init.py b/tests/components/onkyo/test_init.py index 17086a3088e..4c6ddcca214 100644 --- a/tests/components/onkyo/test_init.py +++ b/tests/components/onkyo/test_init.py @@ -33,26 +33,6 @@ async def test_load_unload_entry( assert config_entry.state is ConfigEntryState.NOT_LOADED -async def test_update_entry( - hass: HomeAssistant, - config_entry: MockConfigEntry, -) -> None: - """Test update options.""" - - with patch.object(hass.config_entries, "async_reload", return_value=True): - config_entry = create_empty_config_entry() - receiver_info = create_receiver_info(1) - await setup_integration(hass, config_entry, receiver_info) - - # Force option change - assert hass.config_entries.async_update_entry( - config_entry, options={"option": "new_value"} - ) - await hass.async_block_till_done() - - hass.config_entries.async_reload.assert_called_with(config_entry.entry_id) - - async def test_no_connection( hass: HomeAssistant, config_entry: MockConfigEntry, diff --git a/tests/components/rainbird/test_init.py b/tests/components/rainbird/test_init.py index 01e0c4458e4..520f8578c6e 100644 --- a/tests/components/rainbird/test_init.py +++ b/tests/components/rainbird/test_init.py @@ -449,3 +449,75 @@ async def test_fix_duplicate_device_ids( assert device_entry.identifiers == {(DOMAIN, MAC_ADDRESS_UNIQUE_ID)} assert device_entry.name_by_user == expected_device_name assert device_entry.disabled_by == expected_disabled_by + + +async def test_reload_migration_with_leading_zero_mac( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test migration and reload of a device with a mac address with a leading zero.""" + mac_address = "01:02:03:04:05:06" + mac_address_unique_id = dr.format_mac(mac_address) + serial_number = "0" + + # Setup the config entry to be in a pre-migrated state + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=serial_number, + data={ + "host": "127.0.0.1", + "password": "password", + CONF_MAC: mac_address, + "serial_number": serial_number, + }, + ) + config_entry.add_to_hass(hass) + + # Create a device and entity with the old unique id format + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, f"{serial_number}-1")}, + ) + entity_entry = entity_registry.async_get_or_create( + "switch", + DOMAIN, + f"{serial_number}-1-zone1", + suggested_object_id="zone1", + config_entry=config_entry, + device_id=device_entry.id, + ) + + # Setup the integration, which will migrate the unique ids + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + # Verify the device and entity were migrated to the new format + migrated_device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mac_address_unique_id}-1")} + ) + assert migrated_device_entry is not None + migrated_entity_entry = entity_registry.async_get(entity_entry.entity_id) + assert migrated_entity_entry is not None + assert migrated_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1" + + # Reload the integration + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() + + # Verify the device and entity still have the correct identifiers and were not duplicated + reloaded_device_entry = device_registry.async_get(migrated_device_entry.id) + assert reloaded_device_entry is not None + assert reloaded_device_entry.identifiers == {(DOMAIN, f"{mac_address_unique_id}-1")} + reloaded_entity_entry = entity_registry.async_get(entity_entry.entity_id) + assert reloaded_entity_entry is not None + assert reloaded_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1" + + assert ( + len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)) + == 1 + ) + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 1 + ) diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index e439d3dff93..10eefccace9 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -180,26 +180,6 @@ async def test_credential_error_three( assert (HOMEASSISTANT_DOMAIN, issue_id) in issue_registry.issues -async def test_entry_reloading( - hass: HomeAssistant, - config_entry: MockConfigEntry, - reolink_host: MagicMock, -) -> None: - """Test the entry is reloaded correctly when settings change.""" - reolink_host.is_nvr = False - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert reolink_host.logout.call_count == 0 - assert config_entry.title == "test_reolink_name" - - hass.config_entries.async_update_entry(config_entry, title="New Name") - await hass.async_block_till_done() - - assert reolink_host.logout.call_count == 1 - assert config_entry.title == "New Name" - - @pytest.mark.parametrize( ("attr", "value", "expected_models"), [ diff --git a/tests/components/squeezebox/snapshots/test_init.ambr b/tests/components/squeezebox/snapshots/test_init.ambr new file mode 100644 index 00000000000..3fc65be834a --- /dev/null +++ b/tests/components/squeezebox/snapshots/test_init.ambr @@ -0,0 +1,79 @@ +# serializer version: 1 +# name: test_device_registry + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'squeezebox', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Ralph Irving & Adrian Smith', + 'model': 'SqueezeLite', + 'model_id': None, + 'name': 'Test Player', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '', + 'via_device_id': , + }) +# --- +# name: test_device_registry_server_merged + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + 'ff:ee:dd:cc:bb:aa', + ), + }), + 'disabled_by': None, + 'entry_type': , + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'squeezebox', + '12345678-1234-1234-1234-123456789012', + ), + tuple( + 'squeezebox', + 'ff:ee:dd:cc:bb:aa', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'https://lyrion.org/ / Ralph Irving & Adrian Smith', + 'model': 'Lyrion Music Server/SqueezeLite', + 'model_id': 'LMS', + 'name': '1.2.3.4', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '', + 'via_device_id': , + }) +# --- diff --git a/tests/components/squeezebox/snapshots/test_media_player.ambr b/tests/components/squeezebox/snapshots/test_media_player.ambr index d86c839019c..183b5ca767f 100644 --- a/tests/components/squeezebox/snapshots/test_media_player.ambr +++ b/tests/components/squeezebox/snapshots/test_media_player.ambr @@ -1,82 +1,4 @@ # serializer version: 1 -# name: test_device_registry - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'config_entries_subentries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - 'aa:bb:cc:dd:ee:ff', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'squeezebox', - 'aa:bb:cc:dd:ee:ff', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'Ralph Irving & Adrian Smith', - 'model': 'SqueezeLite', - 'model_id': None, - 'name': 'Test Player', - 'name_by_user': None, - 'primary_config_entry': , - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '', - 'via_device_id': , - }) -# --- -# name: test_device_registry_server_merged - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'config_entries_subentries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - 'ff:ee:dd:cc:bb:aa', - ), - }), - 'disabled_by': None, - 'entry_type': , - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'squeezebox', - '12345678-1234-1234-1234-123456789012', - ), - tuple( - 'squeezebox', - 'ff:ee:dd:cc:bb:aa', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'https://lyrion.org/ / Ralph Irving & Adrian Smith', - 'model': 'Lyrion Music Server/SqueezeLite', - 'model_id': 'LMS', - 'name': '1.2.3.4', - 'name_by_user': None, - 'primary_config_entry': , - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '', - 'via_device_id': , - }) -# --- # name: test_entity_registry[media_player.test_player-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/squeezebox/test_init.py b/tests/components/squeezebox/test_init.py index f70782b13da..5cb7e19abb5 100644 --- a/tests/components/squeezebox/test_init.py +++ b/tests/components/squeezebox/test_init.py @@ -1,10 +1,16 @@ """Test squeezebox initialization.""" from http import HTTPStatus -from unittest.mock import patch +from unittest.mock import MagicMock, patch +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.squeezebox.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceRegistry + +from .conftest import TEST_MAC from tests.common import MockConfigEntry @@ -82,3 +88,27 @@ async def test_init_missing_uuid( mock_async_query.assert_called_once_with( "serverstatus", "-", "-", "prefs:libraryname" ) + + +async def test_device_registry( + hass: HomeAssistant, + device_registry: DeviceRegistry, + configured_player: MagicMock, + snapshot: SnapshotAssertion, +) -> None: + """Test squeezebox device registered in the device registry.""" + reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_MAC[0])}) + assert reg_device is not None + assert reg_device == snapshot + + +async def test_device_registry_server_merged( + hass: HomeAssistant, + device_registry: DeviceRegistry, + configured_players: MagicMock, + snapshot: SnapshotAssertion, +) -> None: + """Test squeezebox device registered in the device registry.""" + reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_MAC[2])}) + assert reg_device is not None + assert reg_device == snapshot diff --git a/tests/components/squeezebox/test_media_player.py b/tests/components/squeezebox/test_media_player.py index e1f480e33a0..440f682370b 100644 --- a/tests/components/squeezebox/test_media_player.py +++ b/tests/components/squeezebox/test_media_player.py @@ -68,7 +68,6 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError -from homeassistant.helpers.device_registry import DeviceRegistry from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util.dt import utcnow @@ -82,30 +81,6 @@ from .conftest import ( from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform -async def test_device_registry( - hass: HomeAssistant, - device_registry: DeviceRegistry, - configured_player: MagicMock, - snapshot: SnapshotAssertion, -) -> None: - """Test squeezebox device registered in the device registry.""" - reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_MAC[0])}) - assert reg_device is not None - assert reg_device == snapshot - - -async def test_device_registry_server_merged( - hass: HomeAssistant, - device_registry: DeviceRegistry, - configured_players: MagicMock, - snapshot: SnapshotAssertion, -) -> None: - """Test squeezebox device registered in the device registry.""" - reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_MAC[2])}) - assert reg_device is not None - assert reg_device == snapshot - - async def test_entity_registry( hass: HomeAssistant, entity_registry: EntityRegistry, @@ -170,30 +145,21 @@ async def test_squeezebox_player_rediscovery( assert hass.states.get("media_player.test_player").state == MediaPlayerState.IDLE -async def test_squeezebox_turn_on( - hass: HomeAssistant, configured_player: MagicMock +@pytest.mark.parametrize( + ("service", "state"), + [(SERVICE_TURN_ON, True), (SERVICE_TURN_OFF, False)], +) +async def test_squeezebox_turn_on_off( + hass: HomeAssistant, configured_player: MagicMock, service: str, state: bool ) -> None: """Test turn on service call.""" await hass.services.async_call( MEDIA_PLAYER_DOMAIN, - SERVICE_TURN_ON, + service, {ATTR_ENTITY_ID: "media_player.test_player"}, blocking=True, ) - configured_player.async_set_power.assert_called_once_with(True) - - -async def test_squeezebox_turn_off( - hass: HomeAssistant, configured_player: MagicMock -) -> None: - """Test turn off service call.""" - await hass.services.async_call( - MEDIA_PLAYER_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "media_player.test_player"}, - blocking=True, - ) - configured_player.async_set_power.assert_called_once_with(False) + configured_player.async_set_power.assert_called_once_with(state) async def test_squeezebox_state( @@ -535,7 +501,10 @@ async def test_squeezebox_play_media_with_announce_volume_invalid( hass: HomeAssistant, configured_player: MagicMock, announce_volume: str | int ) -> None: """Test play service call with announce and volume zero.""" - with pytest.raises(ServiceValidationError): + with pytest.raises( + ServiceValidationError, + match="announce_volume must be a number greater than 0 and less than or equal to 1", + ): await hass.services.async_call( MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 3064c66f009..3156327f1a5 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -12,7 +12,6 @@ from uiprotect.data import NVR, Bootstrap, CloudAccount, Light from homeassistant.components.unifiprotect.const import ( AUTH_RETRIES, CONF_ALLOW_EA, - CONF_DISABLE_RTSP, DOMAIN, ) from homeassistant.components.unifiprotect.data import ( @@ -87,22 +86,6 @@ async def test_setup_multiple( assert mock_config.unique_id == ufp.api.bootstrap.nvr.mac -async def test_reload(hass: HomeAssistant, ufp: MockUFPFixture) -> None: - """Test updating entry reload entry.""" - - await hass.config_entries.async_setup(ufp.entry.entry_id) - await hass.async_block_till_done() - assert ufp.entry.state is ConfigEntryState.LOADED - - options = dict(ufp.entry.options) - options[CONF_DISABLE_RTSP] = True - hass.config_entries.async_update_entry(ufp.entry, options=options) - await hass.async_block_till_done() - - assert ufp.entry.state is ConfigEntryState.LOADED - assert ufp.api.async_disconnect_ws.called - - async def test_unload(hass: HomeAssistant, ufp: MockUFPFixture, light: Light) -> None: """Test unloading of unifiprotect entry.""" diff --git a/tests/components/webostv/test_init.py b/tests/components/webostv/test_init.py index cd8f443c8fd..d7fb12c2848 100644 --- a/tests/components/webostv/test_init.py +++ b/tests/components/webostv/test_init.py @@ -54,6 +54,7 @@ async def test_update_options(hass: HomeAssistant, client) -> None: new_options = config_entry.options.copy() new_options[CONF_SOURCES] = ["Input02", "Live TV"] hass.config_entries.async_update_entry(config_entry, options=new_options) + await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 57635a8cb74..90e731f3fe9 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -373,6 +373,7 @@ async def test_single_segment_with_keep_main_light( hass.config_entries.async_update_entry( init_integration, options={CONF_KEEP_MAIN_LIGHT: True} ) + await hass.config_entries.async_reload(init_integration.entry_id) await hass.async_block_till_done() assert (state := hass.states.get("light.wled_rgb_light_main")) diff --git a/tests/components/workday/test_init.py b/tests/components/workday/test_init.py index f288c340d9f..653b6810197 100644 --- a/tests/components/workday/test_init.py +++ b/tests/components/workday/test_init.py @@ -45,6 +45,7 @@ async def test_update_options( new_options["add_holidays"] = ["2023-04-12"] hass.config_entries.async_update_entry(entry, options=new_options) + await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() entry_check = hass.config_entries.async_get_entry("1") diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 7fb632e18b5..9666e8ba1c4 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -15,6 +15,7 @@ from freezegun import freeze_time from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion +import voluptuous as vol from homeassistant import config_entries, data_entry_flow, loader from homeassistant.config_entries import ConfigEntry @@ -8656,6 +8657,95 @@ async def test_options_flow_config_entry( assert result["reason"] == "abort" +@pytest.mark.parametrize( + ( + "option_flow_base_class", + "number_of_update_listeners", + "expected_configure_result", + "expected_number_of_unloads", + ), + [ + (config_entries.OptionsFlow, 0, does_not_raise(), 0), + (config_entries.OptionsFlowWithReload, 0, does_not_raise(), 1), + (config_entries.OptionsFlow, 1, does_not_raise(), 0), + ( + config_entries.OptionsFlowWithReload, + 1, + pytest.raises( + ValueError, + match="Config entry update listeners should not be used with OptionsFlowWithReload", + ), + 0, + ), + ], +) +async def test_options_flow_automatic_reload( + hass: HomeAssistant, + manager: config_entries.ConfigEntries, + option_flow_base_class: type[config_entries.OptionsFlow], + number_of_update_listeners: int, + expected_configure_result: AbstractContextManager, + expected_number_of_unloads: int, +) -> None: + """Test options flow with automatic reload when updated.""" + original_entry = MockConfigEntry( + domain="test", title="Test", data={}, options={"test": "first"} + ) + original_entry.add_to_hass(hass) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Mock setup entry.""" + for _ in range(number_of_update_listeners): + entry.add_update_listener(Mock()) + return True + + unload_entry_mock = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "test", + async_setup_entry=async_setup_entry, + async_unload_entry=unload_entry_mock, + ), + ) + mock_platform(hass, "test.config_flow", None) + + await hass.config_entries.async_setup(original_entry.entry_id) + assert original_entry.state is config_entries.ConfigEntryState.LOADED + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Test options flow.""" + + class _OptionsFlow(option_flow_base_class): + """Test flow.""" + + async def async_step_init(self, user_input=None): + """Test user step.""" + if user_input is not None: + return self.async_create_entry(data=user_input) + return self.async_show_form( + step_id="init", data_schema=vol.Schema({"test": str}) + ) + + return _OptionsFlow() + + with mock_config_flow("test", TestFlow): + result = await hass.config_entries.options.async_init(original_entry.entry_id) + with expected_configure_result: + await hass.config_entries.options.async_configure( + result["flow_id"], {"test": "updated"} + ) + await hass.async_block_till_done(wait_background_tasks=True) + + assert len(unload_entry_mock.mock_calls) == expected_number_of_unloads + + @pytest.mark.parametrize("integration_frame_path", ["custom_components/my_integration"]) @pytest.mark.usefixtures("mock_integration_frame") async def test_options_flow_deprecated_config_entry_setter(