diff --git a/homeassistant/components/crownstone/config_flow.py b/homeassistant/components/crownstone/config_flow.py index 4cfbb10a4bd..bf6e9204714 100644 --- a/homeassistant/components/crownstone/config_flow.py +++ b/homeassistant/components/crownstone/config_flow.py @@ -143,7 +143,7 @@ class CrownstoneConfigFlowHandler(BaseCrownstoneFlowHandler, ConfigFlow, domain= config_entry: ConfigEntry, ) -> CrownstoneOptionsFlowHandler: """Return the Crownstone options.""" - return CrownstoneOptionsFlowHandler() + return CrownstoneOptionsFlowHandler(config_entry) def __init__(self) -> None: """Initialize the flow.""" @@ -210,9 +210,10 @@ class CrownstoneConfigFlowHandler(BaseCrownstoneFlowHandler, ConfigFlow, domain= class CrownstoneOptionsFlowHandler(BaseCrownstoneFlowHandler, OptionsFlow): """Handle Crownstone options.""" - def __init__(self) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Crownstone options.""" super().__init__(OPTIONS_FLOW, self.async_create_new_entry) + self.options = config_entry.options.copy() async def async_step_init( self, user_input: dict[str, Any] | None = None diff --git a/homeassistant/components/demo/config_flow.py b/homeassistant/components/demo/config_flow.py index 2b27689bdaf..53c1678aa81 100644 --- a/homeassistant/components/demo/config_flow.py +++ b/homeassistant/components/demo/config_flow.py @@ -35,7 +35,7 @@ class DemoConfigFlow(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" - return OptionsFlowHandler() + return OptionsFlowHandler(config_entry) async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: """Set the config entry up from yaml.""" @@ -45,6 +45,10 @@ class DemoConfigFlow(ConfigFlow, domain=DOMAIN): class OptionsFlowHandler(OptionsFlow): """Handle options.""" + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.options = dict(config_entry.options) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/nmap_tracker/config_flow.py b/homeassistant/components/nmap_tracker/config_flow.py index 36645278bae..e05150995aa 100644 --- a/homeassistant/components/nmap_tracker/config_flow.py +++ b/homeassistant/components/nmap_tracker/config_flow.py @@ -141,6 +141,10 @@ async def _async_build_schema_with_user_input( class OptionsFlowHandler(OptionsFlow): """Handle a option flow for homekit.""" + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.options = dict(config_entry.options) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -211,4 +215,4 @@ class NmapTrackerConfigFlow(ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler: """Get the options flow for this handler.""" - return OptionsFlowHandler() + return OptionsFlowHandler(config_entry) diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 3ee0563410c..abb4c884974 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations +from copy import deepcopy from typing import Any import voluptuous as vol @@ -104,7 +105,7 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> OnewireOptionsFlowHandler: """Get the options flow for this handler.""" - return OnewireOptionsFlowHandler() + return OnewireOptionsFlowHandler(config_entry) class OnewireOptionsFlowHandler(OptionsFlow): @@ -125,6 +126,10 @@ class OnewireOptionsFlowHandler(OptionsFlow): current_device: str """Friendly name of the currently selected device.""" + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.options = deepcopy(dict(config_entry.options)) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 830f74b94e8..66e566af0bf 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -109,7 +109,7 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> OnvifOptionsFlowHandler: """Get the options flow for this handler.""" - return OnvifOptionsFlowHandler() + return OnvifOptionsFlowHandler(config_entry) def __init__(self) -> None: """Initialize the ONVIF config flow.""" @@ -389,6 +389,10 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN): class OnvifOptionsFlowHandler(OptionsFlow): """Handle ONVIF options.""" + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize ONVIF options flow.""" + self.options = dict(config_entry.options) + async def async_step_init(self, user_input: None = None) -> ConfigFlowResult: """Manage the ONVIF options.""" return await self.async_step_onvif_devices() diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 22069310804..ae7cbb12574 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping +from copy import deepcopy import logging from typing import TYPE_CHECKING, Any @@ -384,6 +385,7 @@ class PlexOptionsFlowHandler(OptionsFlow): def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Plex options flow.""" + self.options = deepcopy(dict(config_entry.options)) self.server_id = config_entry.data[CONF_SERVER_IDENTIFIER] async def async_step_init(self, user_input: None = None) -> ConfigFlowResult: diff --git a/homeassistant/components/roborock/config_flow.py b/homeassistant/components/roborock/config_flow.py index e01bb904adf..200614b024e 100644 --- a/homeassistant/components/roborock/config_flow.py +++ b/homeassistant/components/roborock/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping +from copy import deepcopy import logging from typing import Any @@ -172,12 +173,16 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> RoborockOptionsFlowHandler: """Create the options flow.""" - return RoborockOptionsFlowHandler() + return RoborockOptionsFlowHandler(config_entry) class RoborockOptionsFlowHandler(OptionsFlow): """Handle an option flow for Roborock.""" + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.options = deepcopy(dict(config_entry.options)) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/sia/config_flow.py b/homeassistant/components/sia/config_flow.py index c421151f7bb..a23978145e7 100644 --- a/homeassistant/components/sia/config_flow.py +++ b/homeassistant/components/sia/config_flow.py @@ -103,7 +103,7 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> SIAOptionsFlowHandler: """Get the options flow for this handler.""" - return SIAOptionsFlowHandler() + return SIAOptionsFlowHandler(config_entry) def __init__(self) -> None: """Initialize the config flow.""" @@ -179,8 +179,9 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN): class SIAOptionsFlowHandler(OptionsFlow): """Handle SIA options.""" - def __init__(self) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize SIA options flow.""" + self.options = deepcopy(dict(config_entry.options)) self.hub: SIAHub | None = None self.accounts_todo: list = [] diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index f92c4909dd5..c2d85160175 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations +from copy import deepcopy import logging from typing import Any @@ -121,14 +122,15 @@ class SomfyConfigFlow(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" - return OptionsFlowHandler() + return OptionsFlowHandler(config_entry) class OptionsFlowHandler(OptionsFlow): """Handle a option flow for somfy_mylink.""" - def __init__(self) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" + self.options = deepcopy(dict(config_entry.options)) self._target_id: str | None = None @callback diff --git a/homeassistant/components/steam_online/config_flow.py b/homeassistant/components/steam_online/config_flow.py index 605f27edb19..69009fca8c4 100644 --- a/homeassistant/components/steam_online/config_flow.py +++ b/homeassistant/components/steam_online/config_flow.py @@ -42,7 +42,7 @@ class SteamFlowHandler(ConfigFlow, domain=DOMAIN): config_entry: SteamConfigEntry, ) -> SteamOptionsFlowHandler: """Get the options flow for this handler.""" - return SteamOptionsFlowHandler() + return SteamOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -121,6 +121,10 @@ def _batch_ids(ids: list[str]) -> Iterator[list[str]]: class SteamOptionsFlowHandler(OptionsFlow): """Handle Steam client options.""" + def __init__(self, entry: SteamConfigEntry) -> None: + """Initialize options flow.""" + self.options = dict(entry.options) + async def async_step_init( self, user_input: dict[str, dict[str, str]] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 44969191fe6..63c8533aa2e 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -21,7 +21,6 @@ import voluptuous as vol from homeassistant.components import ssdp from homeassistant.config_entries import ( SOURCE_REAUTH, - ConfigEntry, ConfigEntryState, ConfigFlow, ConfigFlowResult, @@ -38,6 +37,7 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac +from . import UnifiConfigEntry from .const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, @@ -78,10 +78,10 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: ConfigEntry, + config_entry: UnifiConfigEntry, ) -> UnifiOptionsFlowHandler: """Get the options flow for this handler.""" - return UnifiOptionsFlowHandler() + return UnifiOptionsFlowHandler(config_entry) def __init__(self) -> None: """Initialize the UniFi Network flow.""" @@ -247,6 +247,10 @@ class UnifiOptionsFlowHandler(OptionsFlow): hub: UnifiHub + def __init__(self, config_entry: UnifiConfigEntry) -> None: + """Initialize UniFi Network options flow.""" + self.options = dict(config_entry.options) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6a95707dcda..a13225c4dfe 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -3060,7 +3060,6 @@ class OptionsFlowManager( class OptionsFlow(ConfigEntryBaseFlow): """Base class for config options flows.""" - _options: dict[str, Any] handler: str _config_entry: ConfigEntry @@ -3127,28 +3126,6 @@ class OptionsFlow(ConfigEntryBaseFlow): ) self._config_entry = value - @property - def options(self) -> dict[str, Any]: - """Return a mutable copy of the config entry options. - - Please note that this is not available inside `__init__` method, and - can only be referenced after initialisation. - """ - if not hasattr(self, "_options"): - self._options = deepcopy(dict(self.config_entry.options)) - return self._options - - @options.setter - def options(self, value: dict[str, Any]) -> None: - """Set the options value.""" - report( - "sets option flow options explicitly, which is deprecated " - "and will stop working in 2025.12", - error_if_integration=False, - error_if_core=True, - ) - self._options = value - class OptionsFlowWithConfigEntry(OptionsFlow): """Base class for options flows with config entry and options.""" @@ -3164,6 +3141,11 @@ class OptionsFlowWithConfigEntry(OptionsFlow): error_if_core=True, ) + @property + def options(self) -> dict[str, Any]: + """Return a mutable copy of the config entry options.""" + return self._options + class EntityRegistryDisabledHandler: """Handler when entities related to config entries updated disabled_by.""" diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index b956a58398a..af8c4c6402d 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -421,8 +421,6 @@ class SchemaOptionsFlowHandler(OptionsFlow): options, which is the union of stored options and user input from the options flow steps. """ - # Although `self.options` is most likely unused, it is safer to keep both - # `self.options` and `self._common_handler.options` referring to the same object self._options = copy.deepcopy(dict(config_entry.options)) self._common_handler = SchemaCommonFlowHandler(self, options_flow, self.options) self._async_options_flow_finished = async_options_flow_finished @@ -437,6 +435,11 @@ class SchemaOptionsFlowHandler(OptionsFlow): if async_setup_preview: setattr(self, "async_setup_preview", async_setup_preview) + @property + def options(self) -> dict[str, Any]: + """Return a mutable copy of the config entry options.""" + return self._options + @staticmethod def _async_step( step_id: str, diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 700840eb90e..3e3f3b4c504 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -5066,31 +5066,6 @@ async def test_options_flow_with_config_entry(caplog: pytest.LogCaptureFixture) assert entry.options == {"sub_dict": {"1": "one"}, "sub_list": ["one"]} -@pytest.mark.usefixtures("mock_integration_frame") -@patch.object(frame, "_REPORTED_INTEGRATIONS", set()) -async def test_options_flow_options_not_mutated(hass: HomeAssistant) -> None: - """Test that OptionsFlow doesn't mutate entry options.""" - entry = MockConfigEntry( - domain="test", - data={"first": True}, - options={"sub_dict": {"1": "one"}, "sub_list": ["one"]}, - ) - entry.add_to_hass(hass) - - options_flow = config_entries.OptionsFlow() - options_flow.handler = entry.entry_id - options_flow.hass = hass - - options_flow.options["sub_dict"]["2"] = "two" - options_flow._options["sub_list"].append("two") - - assert options_flow._options == { - "sub_dict": {"1": "one", "2": "two"}, - "sub_list": ["one", "two"], - } - assert entry.options == {"sub_dict": {"1": "one"}, "sub_list": ["one"]} - - async def test_initializing_flows_canceled_on_shutdown( hass: HomeAssistant, manager: config_entries.ConfigEntries ) -> None: @@ -7466,6 +7441,7 @@ async def test_options_flow_config_entry( @pytest.mark.usefixtures("mock_integration_frame") +@patch.object(frame, "_REPORTED_INTEGRATIONS", set()) async def test_options_flow_deprecated_config_entry_setter( hass: HomeAssistant, manager: config_entries.ConfigEntries, @@ -7493,10 +7469,7 @@ async def test_options_flow_deprecated_config_entry_setter( def __init__(self, entry) -> None: """Test initialisation.""" - with patch.object(frame, "_REPORTED_INTEGRATIONS", set()): - self.config_entry = entry - with patch.object(frame, "_REPORTED_INTEGRATIONS", set()): - self.options = entry.options + self.config_entry = entry async def async_step_init(self, user_input=None): """Test user step.""" @@ -7525,10 +7498,6 @@ async def test_options_flow_deprecated_config_entry_setter( "Detected that integration 'hue' sets option flow config_entry explicitly, " "which is deprecated and will stop working in 2025.12" in caplog.text ) - assert ( - "Detected that integration 'hue' sets option flow options explicitly, " - "which is deprecated and will stop working in 2025.12" in caplog.text - ) async def test_add_description_placeholder_automatically(