Remove options property from OptionFlow (#129890)

* Remove options property from OptionFlow

* Update test_config_entries.py

* Partial revert of "Remove deprecated property setters in option flows (#129773)"

* Partial revert "Use new helper properties in crownstone options flow (#129774)"

* Restore onewire init

* Restore onvif

* Restore roborock

* Use deepcopy in onewire

* Restore steam_online

* Restore initial options property in OptionsFlowWithConfigEntry

* re-add options property in SchemaOptionsFlowHandler

* Restore test

* Cleanup
pull/129996/head
epenet 2024-11-06 23:28:01 +01:00 committed by GitHub
parent 53c486ccd1
commit 03d5b18974
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 63 additions and 73 deletions

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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 = []

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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."""

View File

@ -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,

View File

@ -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(