diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 5679eeabcc7..fa37e9ffa1c 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -5,6 +5,7 @@ import asyncio from collections import ChainMap from collections.abc import Callable, Coroutine, Generator, Iterable, Mapping from contextvars import ContextVar +from copy import deepcopy from enum import Enum import functools import logging @@ -1672,11 +1673,20 @@ class OptionsFlowManager(data_entry_flow.FlowManager): class OptionsFlow(data_entry_flow.FlowHandler): - """Base class for config option flows.""" + """Base class for config options flows.""" handler: str +class OptionsFlowWithConfigEntry(OptionsFlow): + """Base class for options flows with config entry and options.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self._options = deepcopy(dict(config_entry.options)) + + class EntityRegistryDisabledHandler: """Handler to handle when entities related to config entries updating disabled_by.""" diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index 405d9333776..13797483172 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -75,12 +75,12 @@ class SchemaCommonFlowHandler: self, handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler, flow: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep], - config_entry: config_entries.ConfigEntry | None, + options: dict[str, Any] | None, ) -> None: """Initialize a common handler.""" self._flow = flow self._handler = handler - self._options = dict(config_entry.options) if config_entry is not None else {} + self._options = options if options is not None else {} async def async_step( self, step_id: str, user_input: dict[str, Any] | None = None @@ -300,7 +300,7 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow): ) -class SchemaOptionsFlowHandler(config_entries.OptionsFlow): +class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): """Handle a schema based options flow.""" def __init__( @@ -310,8 +310,10 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlow): async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]], None], ) -> None: """Initialize options flow.""" - self._common_handler = SchemaCommonFlowHandler(self, options_flow, config_entry) - self.config_entry = config_entry + super().__init__(config_entry) + self._common_handler = SchemaCommonFlowHandler( + self, options_flow, self._options + ) self._async_options_flow_finished = async_options_flow_finished for step in options_flow: diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 272b3d321b8..68ece465226 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -3426,3 +3426,23 @@ async def test_async_wait_component_startup(hass: HomeAssistant): # The component has been loaded assert "test" in hass.config.components + + +async def test_options_flow_options_not_mutated() -> None: + """Test that OptionsFlowWithConfigEntry doesn't mutate entry options.""" + entry = MockConfigEntry( + domain="test", + data={"first": True}, + options={"sub_dict": {"1": "one"}, "sub_list": ["one"]}, + ) + + options_flow = config_entries.OptionsFlowWithConfigEntry(entry) + + 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"]}