Add OptionsFlow helper for a mutable copy of the config entry options (#129718)
* Add OptionsFlow helper for a mutable copy of the config entry options * Add tests * Improve coverage * error_if_core=False * Adjust report * Avoid mutli-line ternarypull/129779/head
parent
d671d48869
commit
89eb395e2d
|
@ -220,7 +220,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
config_entry: ConfigEntry,
|
||||
) -> MQTTOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return MQTTOptionsFlowHandler(config_entry)
|
||||
return MQTTOptionsFlowHandler()
|
||||
|
||||
async def _async_install_addon(self) -> None:
|
||||
"""Install the Mosquitto Mqtt broker add-on."""
|
||||
|
@ -543,11 +543,9 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
class MQTTOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle MQTT options."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self) -> None:
|
||||
"""Initialize MQTT options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.broker_config: dict[str, str | int] = {}
|
||||
self.options = config_entry.options
|
||||
|
||||
async def async_step_init(self, user_input: None = None) -> ConfigFlowResult:
|
||||
"""Manage the MQTT options."""
|
||||
|
|
|
@ -109,7 +109,7 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
config_entry: ConfigEntry,
|
||||
) -> OnvifOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return OnvifOptionsFlowHandler(config_entry)
|
||||
return OnvifOptionsFlowHandler()
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the ONVIF config flow."""
|
||||
|
@ -389,11 +389,6 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
class OnvifOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle ONVIF options."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize ONVIF options flow."""
|
||||
self.config_entry = config_entry
|
||||
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()
|
||||
|
|
|
@ -170,8 +170,6 @@ class OptionsFlowHandler(OptionsFlow):
|
|||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.options = config_entry.options
|
||||
self.host = config_entry.data[CONF_HOST]
|
||||
self.key = config_entry.data[CONF_CLIENT_SECRET]
|
||||
|
||||
|
@ -188,7 +186,8 @@ class OptionsFlowHandler(OptionsFlow):
|
|||
if not sources_list:
|
||||
errors["base"] = "cannot_retrieve"
|
||||
|
||||
sources = [s for s in self.options.get(CONF_SOURCES, []) if s in sources_list]
|
||||
option_sources = self.config_entry.options.get(CONF_SOURCES, [])
|
||||
sources = [s for s in option_sources if s in sources_list]
|
||||
if not sources:
|
||||
sources = sources_list
|
||||
|
||||
|
|
|
@ -3053,6 +3053,7 @@ class OptionsFlowManager(
|
|||
class OptionsFlow(ConfigEntryBaseFlow):
|
||||
"""Base class for config options flows."""
|
||||
|
||||
_options: dict[str, Any]
|
||||
handler: str
|
||||
|
||||
_config_entry: ConfigEntry
|
||||
|
@ -3119,6 +3120,28 @@ 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."""
|
||||
|
@ -3127,11 +3150,12 @@ class OptionsFlowWithConfigEntry(OptionsFlow):
|
|||
"""Initialize options flow."""
|
||||
self._config_entry = config_entry
|
||||
self._options = deepcopy(dict(config_entry.options))
|
||||
|
||||
@property
|
||||
def options(self) -> dict[str, Any]:
|
||||
"""Return a mutable copy of the config entry options."""
|
||||
return self._options
|
||||
report(
|
||||
"inherits from OptionsFlowWithConfigEntry, which is deprecated "
|
||||
"and will stop working in 2025.12",
|
||||
error_if_integration=False,
|
||||
error_if_core=False,
|
||||
)
|
||||
|
||||
|
||||
class EntityRegistryDisabledHandler:
|
||||
|
|
|
@ -4812,6 +4812,7 @@ async def test_reauth_reconfigure_missing_entry(
|
|||
|
||||
|
||||
@pytest.mark.usefixtures("mock_integration_frame")
|
||||
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
|
||||
@pytest.mark.parametrize(
|
||||
"source", [config_entries.SOURCE_REAUTH, config_entries.SOURCE_RECONFIGURE]
|
||||
)
|
||||
|
@ -5039,15 +5040,21 @@ async def test_async_wait_component_startup(hass: HomeAssistant) -> None:
|
|||
assert "test" in hass.config.components
|
||||
|
||||
|
||||
async def test_options_flow_options_not_mutated() -> None:
|
||||
@pytest.mark.usefixtures("mock_integration_frame")
|
||||
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
|
||||
async def test_options_flow_with_config_entry(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test that OptionsFlowWithConfigEntry doesn't mutate entry options."""
|
||||
entry = MockConfigEntry(
|
||||
domain="test",
|
||||
domain="hue",
|
||||
data={"first": True},
|
||||
options={"sub_dict": {"1": "one"}, "sub_list": ["one"]},
|
||||
)
|
||||
|
||||
options_flow = config_entries.OptionsFlowWithConfigEntry(entry)
|
||||
assert (
|
||||
"Detected that integration 'hue' inherits from OptionsFlowWithConfigEntry,"
|
||||
" which is deprecated and will stop working in 2025.12" in caplog.text
|
||||
)
|
||||
|
||||
options_flow._options["sub_dict"]["2"] = "two"
|
||||
options_flow._options["sub_list"].append("two")
|
||||
|
@ -5059,6 +5066,31 @@ async def test_options_flow_options_not_mutated() -> None:
|
|||
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:
|
||||
|
@ -7405,7 +7437,6 @@ 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,
|
||||
|
@ -7433,7 +7464,10 @@ async def test_options_flow_deprecated_config_entry_setter(
|
|||
|
||||
def __init__(self, entry) -> None:
|
||||
"""Test initialisation."""
|
||||
self.config_entry = entry
|
||||
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()):
|
||||
self.config_entry = entry
|
||||
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()):
|
||||
self.options = entry.options
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Test user step."""
|
||||
|
@ -7462,6 +7496,10 @@ 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(
|
||||
|
|
Loading…
Reference in New Issue