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
This commit is contained in:
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, config_entry: ConfigEntry,
) -> CrownstoneOptionsFlowHandler: ) -> CrownstoneOptionsFlowHandler:
"""Return the Crownstone options.""" """Return the Crownstone options."""
return CrownstoneOptionsFlowHandler() return CrownstoneOptionsFlowHandler(config_entry)
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the flow.""" """Initialize the flow."""
@ -210,9 +210,10 @@ class CrownstoneConfigFlowHandler(BaseCrownstoneFlowHandler, ConfigFlow, domain=
class CrownstoneOptionsFlowHandler(BaseCrownstoneFlowHandler, OptionsFlow): class CrownstoneOptionsFlowHandler(BaseCrownstoneFlowHandler, OptionsFlow):
"""Handle Crownstone options.""" """Handle Crownstone options."""
def __init__(self) -> None: def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize Crownstone options.""" """Initialize Crownstone options."""
super().__init__(OPTIONS_FLOW, self.async_create_new_entry) super().__init__(OPTIONS_FLOW, self.async_create_new_entry)
self.options = config_entry.options.copy()
async def async_step_init( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None

View File

@ -35,7 +35,7 @@ class DemoConfigFlow(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> OptionsFlowHandler: ) -> OptionsFlowHandler:
"""Get the options flow for this handler.""" """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: async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Set the config entry up from yaml.""" """Set the config entry up from yaml."""
@ -45,6 +45,10 @@ class DemoConfigFlow(ConfigFlow, domain=DOMAIN):
class OptionsFlowHandler(OptionsFlow): class OptionsFlowHandler(OptionsFlow):
"""Handle options.""" """Handle options."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.options = dict(config_entry.options)
async def async_step_init( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -141,6 +141,10 @@ async def _async_build_schema_with_user_input(
class OptionsFlowHandler(OptionsFlow): class OptionsFlowHandler(OptionsFlow):
"""Handle a option flow for homekit.""" """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( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
@ -211,4 +215,4 @@ class NmapTrackerConfigFlow(ConfigFlow, domain=DOMAIN):
@callback @callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler: def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return OptionsFlowHandler() return OptionsFlowHandler(config_entry)

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from copy import deepcopy
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
@ -104,7 +105,7 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> OnewireOptionsFlowHandler: ) -> OnewireOptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return OnewireOptionsFlowHandler() return OnewireOptionsFlowHandler(config_entry)
class OnewireOptionsFlowHandler(OptionsFlow): class OnewireOptionsFlowHandler(OptionsFlow):
@ -125,6 +126,10 @@ class OnewireOptionsFlowHandler(OptionsFlow):
current_device: str current_device: str
"""Friendly name of the currently selected device.""" """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( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -109,7 +109,7 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> OnvifOptionsFlowHandler: ) -> OnvifOptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return OnvifOptionsFlowHandler() return OnvifOptionsFlowHandler(config_entry)
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the ONVIF config flow.""" """Initialize the ONVIF config flow."""
@ -389,6 +389,10 @@ class OnvifFlowHandler(ConfigFlow, domain=DOMAIN):
class OnvifOptionsFlowHandler(OptionsFlow): class OnvifOptionsFlowHandler(OptionsFlow):
"""Handle ONVIF options.""" """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: async def async_step_init(self, user_input: None = None) -> ConfigFlowResult:
"""Manage the ONVIF options.""" """Manage the ONVIF options."""
return await self.async_step_onvif_devices() return await self.async_step_onvif_devices()

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from copy import deepcopy
import logging import logging
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@ -384,6 +385,7 @@ class PlexOptionsFlowHandler(OptionsFlow):
def __init__(self, config_entry: ConfigEntry) -> None: def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize Plex options flow.""" """Initialize Plex options flow."""
self.options = deepcopy(dict(config_entry.options))
self.server_id = config_entry.data[CONF_SERVER_IDENTIFIER] self.server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
async def async_step_init(self, user_input: None = None) -> ConfigFlowResult: async def async_step_init(self, user_input: None = None) -> ConfigFlowResult:

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from copy import deepcopy
import logging import logging
from typing import Any from typing import Any
@ -172,12 +173,16 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> RoborockOptionsFlowHandler: ) -> RoborockOptionsFlowHandler:
"""Create the options flow.""" """Create the options flow."""
return RoborockOptionsFlowHandler() return RoborockOptionsFlowHandler(config_entry)
class RoborockOptionsFlowHandler(OptionsFlow): class RoborockOptionsFlowHandler(OptionsFlow):
"""Handle an option flow for Roborock.""" """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( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -103,7 +103,7 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> SIAOptionsFlowHandler: ) -> SIAOptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return SIAOptionsFlowHandler() return SIAOptionsFlowHandler(config_entry)
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
@ -179,8 +179,9 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN):
class SIAOptionsFlowHandler(OptionsFlow): class SIAOptionsFlowHandler(OptionsFlow):
"""Handle SIA options.""" """Handle SIA options."""
def __init__(self) -> None: def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize SIA options flow.""" """Initialize SIA options flow."""
self.options = deepcopy(dict(config_entry.options))
self.hub: SIAHub | None = None self.hub: SIAHub | None = None
self.accounts_todo: list = [] self.accounts_todo: list = []

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from copy import deepcopy
import logging import logging
from typing import Any from typing import Any
@ -121,14 +122,15 @@ class SomfyConfigFlow(ConfigFlow, domain=DOMAIN):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> OptionsFlowHandler: ) -> OptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return OptionsFlowHandler() return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(OptionsFlow): class OptionsFlowHandler(OptionsFlow):
"""Handle a option flow for somfy_mylink.""" """Handle a option flow for somfy_mylink."""
def __init__(self) -> None: def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow.""" """Initialize options flow."""
self.options = deepcopy(dict(config_entry.options))
self._target_id: str | None = None self._target_id: str | None = None
@callback @callback

View File

@ -42,7 +42,7 @@ class SteamFlowHandler(ConfigFlow, domain=DOMAIN):
config_entry: SteamConfigEntry, config_entry: SteamConfigEntry,
) -> SteamOptionsFlowHandler: ) -> SteamOptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return SteamOptionsFlowHandler() return SteamOptionsFlowHandler(config_entry)
async def async_step_user( async def async_step_user(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
@ -121,6 +121,10 @@ def _batch_ids(ids: list[str]) -> Iterator[list[str]]:
class SteamOptionsFlowHandler(OptionsFlow): class SteamOptionsFlowHandler(OptionsFlow):
"""Handle Steam client options.""" """Handle Steam client options."""
def __init__(self, entry: SteamConfigEntry) -> None:
"""Initialize options flow."""
self.options = dict(entry.options)
async def async_step_init( async def async_step_init(
self, user_input: dict[str, dict[str, str]] | None = None self, user_input: dict[str, dict[str, str]] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -21,7 +21,6 @@ import voluptuous as vol
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.config_entries import ( from homeassistant.config_entries import (
SOURCE_REAUTH, SOURCE_REAUTH,
ConfigEntry,
ConfigEntryState, ConfigEntryState,
ConfigFlow, ConfigFlow,
ConfigFlowResult, ConfigFlowResult,
@ -38,6 +37,7 @@ from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.device_registry import format_mac
from . import UnifiConfigEntry
from .const import ( from .const import (
CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_BANDWIDTH_SENSORS,
CONF_ALLOW_UPTIME_SENSORS, CONF_ALLOW_UPTIME_SENSORS,
@ -78,10 +78,10 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN):
@staticmethod @staticmethod
@callback @callback
def async_get_options_flow( def async_get_options_flow(
config_entry: ConfigEntry, config_entry: UnifiConfigEntry,
) -> UnifiOptionsFlowHandler: ) -> UnifiOptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return UnifiOptionsFlowHandler() return UnifiOptionsFlowHandler(config_entry)
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the UniFi Network flow.""" """Initialize the UniFi Network flow."""
@ -247,6 +247,10 @@ class UnifiOptionsFlowHandler(OptionsFlow):
hub: UnifiHub 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( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -3060,7 +3060,6 @@ class OptionsFlowManager(
class OptionsFlow(ConfigEntryBaseFlow): class OptionsFlow(ConfigEntryBaseFlow):
"""Base class for config options flows.""" """Base class for config options flows."""
_options: dict[str, Any]
handler: str handler: str
_config_entry: ConfigEntry _config_entry: ConfigEntry
@ -3127,28 +3126,6 @@ class OptionsFlow(ConfigEntryBaseFlow):
) )
self._config_entry = value 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): class OptionsFlowWithConfigEntry(OptionsFlow):
"""Base class for options flows with config entry and options.""" """Base class for options flows with config entry and options."""
@ -3164,6 +3141,11 @@ class OptionsFlowWithConfigEntry(OptionsFlow):
error_if_core=True, 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: class EntityRegistryDisabledHandler:
"""Handler when entities related to config entries updated disabled_by.""" """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 options, which is the union of stored options and user input from the options
flow steps. 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._options = copy.deepcopy(dict(config_entry.options))
self._common_handler = SchemaCommonFlowHandler(self, options_flow, self.options) self._common_handler = SchemaCommonFlowHandler(self, options_flow, self.options)
self._async_options_flow_finished = async_options_flow_finished self._async_options_flow_finished = async_options_flow_finished
@ -437,6 +435,11 @@ class SchemaOptionsFlowHandler(OptionsFlow):
if async_setup_preview: if async_setup_preview:
setattr(self, "async_setup_preview", 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 @staticmethod
def _async_step( def _async_step(
step_id: str, 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"]} 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( async def test_initializing_flows_canceled_on_shutdown(
hass: HomeAssistant, manager: config_entries.ConfigEntries hass: HomeAssistant, manager: config_entries.ConfigEntries
) -> None: ) -> None:
@ -7466,6 +7441,7 @@ async def test_options_flow_config_entry(
@pytest.mark.usefixtures("mock_integration_frame") @pytest.mark.usefixtures("mock_integration_frame")
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_options_flow_deprecated_config_entry_setter( async def test_options_flow_deprecated_config_entry_setter(
hass: HomeAssistant, hass: HomeAssistant,
manager: config_entries.ConfigEntries, manager: config_entries.ConfigEntries,
@ -7493,10 +7469,7 @@ async def test_options_flow_deprecated_config_entry_setter(
def __init__(self, entry) -> None: def __init__(self, entry) -> None:
"""Test initialisation.""" """Test initialisation."""
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()): self.config_entry = entry
self.config_entry = entry
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()):
self.options = entry.options
async def async_step_init(self, user_input=None): async def async_step_init(self, user_input=None):
"""Test user step.""" """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, " "Detected that integration 'hue' sets option flow config_entry explicitly, "
"which is deprecated and will stop working in 2025.12" in caplog.text "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( async def test_add_description_placeholder_automatically(