Freeze config entry data (#32615)
* Freeze config entry data * Fix mutating entry.data * Fix config entry options tests
This commit is contained in:
parent
3318e65948
commit
d4615fd432
16 changed files with 71 additions and 45 deletions
|
@ -52,9 +52,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||||
# Check if host needs to be updated
|
# Check if host needs to be updated
|
||||||
entry = entries[0]
|
entry = entries[0]
|
||||||
if entry.data[CONF_HOST] != host:
|
if entry.data[CONF_HOST] != host:
|
||||||
entry.data[CONF_HOST] = host
|
hass.config_entries.async_update_entry(
|
||||||
entry.title = format_title(host)
|
entry, title=format_title(host), data={**entry.data, CONF_HOST: host}
|
||||||
hass.config_entries.async_update_entry(entry)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -568,7 +568,7 @@ async def async_setup_entry(hass, entry):
|
||||||
|
|
||||||
# If user didn't have configuration.yaml config, generate defaults
|
# If user didn't have configuration.yaml config, generate defaults
|
||||||
if conf is None:
|
if conf is None:
|
||||||
conf = CONFIG_SCHEMA({DOMAIN: entry.data})[DOMAIN]
|
conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN]
|
||||||
elif any(key in conf for key in entry.data):
|
elif any(key in conf for key in entry.data):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Data in your configuration entry is going to override your "
|
"Data in your configuration entry is going to override your "
|
||||||
|
|
|
@ -227,7 +227,7 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
|
||||||
def __init__(self, config_entry):
|
def __init__(self, config_entry):
|
||||||
"""Initialize Plex options flow."""
|
"""Initialize Plex options flow."""
|
||||||
self.options = copy.deepcopy(config_entry.options)
|
self.options = copy.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):
|
async def async_step_init(self, user_input=None):
|
||||||
|
|
|
@ -158,13 +158,14 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
if self._id.startswith("uuid:"):
|
if self._id.startswith("uuid:"):
|
||||||
self._id = self._id[5:]
|
self._id = self._id[5:]
|
||||||
|
|
||||||
config_entry = await self.async_set_unique_id(ip_address)
|
await self.async_set_unique_id(ip_address)
|
||||||
if config_entry:
|
self._abort_if_unique_id_configured(
|
||||||
config_entry.data[CONF_ID] = self._id
|
{
|
||||||
config_entry.data[CONF_MANUFACTURER] = self._manufacturer
|
CONF_ID: self._id,
|
||||||
config_entry.data[CONF_MODEL] = self._model
|
CONF_MANUFACTURER: self._manufacturer,
|
||||||
self.hass.config_entries.async_update_entry(config_entry)
|
CONF_MODEL: self._model,
|
||||||
return self.async_abort(reason="already_configured")
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.context["title_placeholders"] = {"model": self._model}
|
self.context["title_placeholders"] = {"model": self._model}
|
||||||
return await self.async_step_confirm()
|
return await self.async_step_confirm()
|
||||||
|
|
|
@ -109,8 +109,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
entry.data[CONF_OAUTH_CLIENT_SECRET],
|
entry.data[CONF_OAUTH_CLIENT_SECRET],
|
||||||
entry.data[CONF_REFRESH_TOKEN],
|
entry.data[CONF_REFRESH_TOKEN],
|
||||||
)
|
)
|
||||||
entry.data[CONF_REFRESH_TOKEN] = token.refresh_token
|
hass.config_entries.async_update_entry(
|
||||||
hass.config_entries.async_update_entry(entry)
|
entry, data={**entry.data, CONF_REFRESH_TOKEN: token.refresh_token}
|
||||||
|
)
|
||||||
|
|
||||||
# Get devices and their current status
|
# Get devices and their current status
|
||||||
devices = await api.devices(location_ids=[installed_app.location_id])
|
devices = await api.devices(location_ids=[installed_app.location_id])
|
||||||
|
@ -304,8 +305,13 @@ class DeviceBroker:
|
||||||
self._entry.data[CONF_OAUTH_CLIENT_ID],
|
self._entry.data[CONF_OAUTH_CLIENT_ID],
|
||||||
self._entry.data[CONF_OAUTH_CLIENT_SECRET],
|
self._entry.data[CONF_OAUTH_CLIENT_SECRET],
|
||||||
)
|
)
|
||||||
self._entry.data[CONF_REFRESH_TOKEN] = self._token.refresh_token
|
self._hass.config_entries.async_update_entry(
|
||||||
self._hass.config_entries.async_update_entry(self._entry)
|
self._entry,
|
||||||
|
data={
|
||||||
|
**self._entry.data,
|
||||||
|
CONF_REFRESH_TOKEN: self._token.refresh_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Regenerated refresh token for installed app: %s",
|
"Regenerated refresh token for installed app: %s",
|
||||||
self._installed_app_id,
|
self._installed_app_id,
|
||||||
|
|
|
@ -428,8 +428,9 @@ async def smartapp_update(hass: HomeAssistantType, req, resp, app):
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if entry:
|
if entry:
|
||||||
entry.data[CONF_REFRESH_TOKEN] = req.refresh_token
|
hass.config_entries.async_update_entry(
|
||||||
hass.config_entries.async_update_entry(entry)
|
entry, data={**entry.data, CONF_REFRESH_TOKEN: req.refresh_token}
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id
|
"Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id
|
||||||
|
|
|
@ -196,7 +196,7 @@ class TransmissionClient:
|
||||||
def add_options(self):
|
def add_options(self):
|
||||||
"""Add options for entry."""
|
"""Add options for entry."""
|
||||||
if not self.config_entry.options:
|
if not self.config_entry.options:
|
||||||
scan_interval = self.config_entry.data.pop(
|
scan_interval = self.config_entry.data.get(
|
||||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||||
)
|
)
|
||||||
options = {CONF_SCAN_INTERVAL: scan_interval}
|
options = {CONF_SCAN_INTERVAL: scan_interval}
|
||||||
|
|
|
@ -151,9 +151,10 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 'register'/save UDN
|
# 'register'/save UDN
|
||||||
config_entry.data["udn"] = device.udn
|
|
||||||
hass.data[DOMAIN]["devices"][device.udn] = device
|
hass.data[DOMAIN]["devices"][device.udn] = device
|
||||||
hass.config_entries.async_update_entry(entry=config_entry, data=config_entry.data)
|
hass.config_entries.async_update_entry(
|
||||||
|
entry=config_entry, data={**config_entry.data, "udn": device.udn}
|
||||||
|
)
|
||||||
|
|
||||||
# create device registry entry
|
# create device registry entry
|
||||||
device_registry = await dr.async_get_registry(hass)
|
device_registry = await dr.async_get_registry(hass)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
from types import MappingProxyType
|
||||||
from typing import Any, Callable, Dict, List, Optional, Set, Union, cast
|
from typing import Any, Callable, Dict, List, Optional, Set, Union, cast
|
||||||
import uuid
|
import uuid
|
||||||
import weakref
|
import weakref
|
||||||
|
@ -139,10 +140,10 @@ class ConfigEntry:
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
# Config data
|
# Config data
|
||||||
self.data = data
|
self.data = MappingProxyType(data)
|
||||||
|
|
||||||
# Entry options
|
# Entry options
|
||||||
self.options = options or {}
|
self.options = MappingProxyType(options or {})
|
||||||
|
|
||||||
# Entry system options
|
# Entry system options
|
||||||
self.system_options = SystemOptions(**system_options)
|
self.system_options = SystemOptions(**system_options)
|
||||||
|
@ -396,8 +397,8 @@ class ConfigEntry:
|
||||||
"version": self.version,
|
"version": self.version,
|
||||||
"domain": self.domain,
|
"domain": self.domain,
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"data": self.data,
|
"data": dict(self.data),
|
||||||
"options": self.options,
|
"options": dict(self.options),
|
||||||
"system_options": self.system_options.as_dict(),
|
"system_options": self.system_options.as_dict(),
|
||||||
"source": self.source,
|
"source": self.source,
|
||||||
"connection_class": self.connection_class,
|
"connection_class": self.connection_class,
|
||||||
|
@ -720,6 +721,7 @@ class ConfigEntries:
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
*,
|
*,
|
||||||
unique_id: Union[str, dict, None] = _UNDEF,
|
unique_id: Union[str, dict, None] = _UNDEF,
|
||||||
|
title: Union[str, dict] = _UNDEF,
|
||||||
data: dict = _UNDEF,
|
data: dict = _UNDEF,
|
||||||
options: dict = _UNDEF,
|
options: dict = _UNDEF,
|
||||||
system_options: dict = _UNDEF,
|
system_options: dict = _UNDEF,
|
||||||
|
@ -728,11 +730,14 @@ class ConfigEntries:
|
||||||
if unique_id is not _UNDEF:
|
if unique_id is not _UNDEF:
|
||||||
entry.unique_id = cast(Optional[str], unique_id)
|
entry.unique_id = cast(Optional[str], unique_id)
|
||||||
|
|
||||||
|
if title is not _UNDEF:
|
||||||
|
entry.title = cast(str, title)
|
||||||
|
|
||||||
if data is not _UNDEF:
|
if data is not _UNDEF:
|
||||||
entry.data = data
|
entry.data = MappingProxyType(data)
|
||||||
|
|
||||||
if options is not _UNDEF:
|
if options is not _UNDEF:
|
||||||
entry.options = options
|
entry.options = MappingProxyType(options)
|
||||||
|
|
||||||
if system_options is not _UNDEF:
|
if system_options is not _UNDEF:
|
||||||
entry.system_options.update(**system_options)
|
entry.system_options.update(**system_options)
|
||||||
|
@ -818,7 +823,9 @@ class ConfigFlow(data_entry_flow.FlowHandler):
|
||||||
raise data_entry_flow.UnknownHandler
|
raise data_entry_flow.UnknownHandler
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _abort_if_unique_id_configured(self, updates: Dict[Any, Any] = None) -> None:
|
def _abort_if_unique_id_configured(
|
||||||
|
self, updates: Optional[Dict[Any, Any]] = None
|
||||||
|
) -> None:
|
||||||
"""Abort if the unique ID is already configured."""
|
"""Abort if the unique ID is already configured."""
|
||||||
assert self.hass
|
assert self.hass
|
||||||
if self.unique_id is None:
|
if self.unique_id is None:
|
||||||
|
|
|
@ -38,7 +38,7 @@ async def test_setup_entry(hass):
|
||||||
async def test_setup_entry_fails(hass):
|
async def test_setup_entry_fails(hass):
|
||||||
"""Test successful setup of entry."""
|
"""Test successful setup of entry."""
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=axis.DOMAIN, data={axis.CONF_MAC: "0123"}, options=True, version=2
|
domain=axis.DOMAIN, data={axis.CONF_MAC: "0123"}, version=2
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ async def test_hap_with_name(hass, mock_connection, hmip_config_entry):
|
||||||
entity_name = f"{home_name} Treppe"
|
entity_name = f"{home_name} Treppe"
|
||||||
device_model = "HmIP-BSL"
|
device_model = "HmIP-BSL"
|
||||||
|
|
||||||
hmip_config_entry.data["name"] = home_name
|
hmip_config_entry.data = {**hmip_config_entry.data, "name": home_name}
|
||||||
mock_hap = await HomeFactory(
|
mock_hap = await HomeFactory(
|
||||||
hass, mock_connection, hmip_config_entry
|
hass, mock_connection, hmip_config_entry
|
||||||
).async_get_mock_hap(test_devices=["Treppe"])
|
).async_get_mock_hap(test_devices=["Treppe"])
|
||||||
|
|
|
@ -73,11 +73,10 @@ async def test_flow_entry_already_exists(hass):
|
||||||
Test when the form should show when user puts existing location
|
Test when the form should show when user puts existing location
|
||||||
in the config gui. Then the form should show with error.
|
in the config gui. Then the form should show with error.
|
||||||
"""
|
"""
|
||||||
first_entry = MockConfigEntry(domain="met")
|
first_entry = MockConfigEntry(
|
||||||
first_entry.data["name"] = "home"
|
domain="met",
|
||||||
first_entry.data[CONF_LONGITUDE] = 0
|
data={"name": "home", CONF_LATITUDE: 0, CONF_LONGITUDE: 0, CONF_ELEVATION: 0},
|
||||||
first_entry.data[CONF_LATITUDE] = 0
|
)
|
||||||
first_entry.data[CONF_ELEVATION] = 0
|
|
||||||
first_entry.add_to_hass(hass)
|
first_entry.add_to_hass(hass)
|
||||||
|
|
||||||
test_data = {
|
test_data = {
|
||||||
|
|
|
@ -33,10 +33,10 @@ async def setup_mikrotik_entry(hass, **kwargs):
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
if "force_dhcp" in kwargs:
|
if "force_dhcp" in kwargs:
|
||||||
config_entry.options["force_dhcp"] = True
|
config_entry.options = {**config_entry.options, "force_dhcp": True}
|
||||||
|
|
||||||
if "arp_ping" in kwargs:
|
if "arp_ping" in kwargs:
|
||||||
config_entry.options["arp_ping"] = True
|
config_entry.options = {**config_entry.options, "arp_ping": True}
|
||||||
|
|
||||||
with patch("librouteros.connect"), patch.object(
|
with patch("librouteros.connect"), patch.object(
|
||||||
mikrotik.hub.MikrotikData, "command", new=mock_command
|
mikrotik.hub.MikrotikData, "command", new=mock_command
|
||||||
|
|
|
@ -316,9 +316,10 @@ async def test_ssdp_already_configured(hass, remote):
|
||||||
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
|
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
|
||||||
)
|
)
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["data"][CONF_MANUFACTURER] is None
|
entry = result["result"]
|
||||||
assert result["data"][CONF_MODEL] is None
|
assert entry.data[CONF_MANUFACTURER] is None
|
||||||
assert result["data"][CONF_ID] is None
|
assert entry.data[CONF_MODEL] is None
|
||||||
|
assert entry.data[CONF_ID] is None
|
||||||
|
|
||||||
# failed as already configured
|
# failed as already configured
|
||||||
result2 = await hass.config_entries.flow.async_init(
|
result2 = await hass.config_entries.flow.async_init(
|
||||||
|
@ -328,9 +329,9 @@ async def test_ssdp_already_configured(hass, remote):
|
||||||
assert result2["reason"] == "already_configured"
|
assert result2["reason"] == "already_configured"
|
||||||
|
|
||||||
# check updated device info
|
# check updated device info
|
||||||
assert result["data"][CONF_MANUFACTURER] == "fake_manufacturer"
|
assert entry.data[CONF_MANUFACTURER] == "fake_manufacturer"
|
||||||
assert result["data"][CONF_MODEL] == "fake_model"
|
assert entry.data[CONF_MODEL] == "fake_model"
|
||||||
assert result["data"][CONF_ID] == "fake_uuid"
|
assert entry.data[CONF_ID] == "fake_uuid"
|
||||||
|
|
||||||
|
|
||||||
async def test_autodetect_websocket(hass, remote):
|
async def test_autodetect_websocket(hass, remote):
|
||||||
|
|
|
@ -423,7 +423,10 @@ async def test_event_handler_dispatches_updated_devices(
|
||||||
data={"codeId": "1"},
|
data={"codeId": "1"},
|
||||||
)
|
)
|
||||||
request = event_request_factory(device_ids=device_ids, events=[event])
|
request = event_request_factory(device_ids=device_ids, events=[event])
|
||||||
config_entry.data[CONF_INSTALLED_APP_ID] = request.installed_app_id
|
config_entry.data = {
|
||||||
|
**config_entry.data,
|
||||||
|
CONF_INSTALLED_APP_ID: request.installed_app_id,
|
||||||
|
}
|
||||||
called = False
|
called = False
|
||||||
|
|
||||||
def signal(ids):
|
def signal(ids):
|
||||||
|
@ -479,7 +482,10 @@ async def test_event_handler_fires_button_events(
|
||||||
device.device_id, capability="button", attribute="button", value="pushed"
|
device.device_id, capability="button", attribute="button", value="pushed"
|
||||||
)
|
)
|
||||||
request = event_request_factory(events=[event])
|
request = event_request_factory(events=[event])
|
||||||
config_entry.data[CONF_INSTALLED_APP_ID] = request.installed_app_id
|
config_entry.data = {
|
||||||
|
**config_entry.data,
|
||||||
|
CONF_INSTALLED_APP_ID: request.installed_app_id,
|
||||||
|
}
|
||||||
called = False
|
called = False
|
||||||
|
|
||||||
def handler(evt):
|
def handler(evt):
|
||||||
|
|
|
@ -456,6 +456,8 @@ async def test_saving_and_loading(hass):
|
||||||
"test", context={"source": config_entries.SOURCE_USER}
|
"test", context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries()) == 2
|
||||||
|
|
||||||
# To trigger the call_later
|
# To trigger the call_later
|
||||||
async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1))
|
async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1))
|
||||||
# To execute the save
|
# To execute the save
|
||||||
|
@ -465,6 +467,8 @@ async def test_saving_and_loading(hass):
|
||||||
manager = config_entries.ConfigEntries(hass, {})
|
manager = config_entries.ConfigEntries(hass, {})
|
||||||
await manager.async_initialize()
|
await manager.async_initialize()
|
||||||
|
|
||||||
|
assert len(manager.async_entries()) == 2
|
||||||
|
|
||||||
# Ensure same order
|
# Ensure same order
|
||||||
for orig, loaded in zip(
|
for orig, loaded in zip(
|
||||||
hass.config_entries.async_entries(), manager.async_entries()
|
hass.config_entries.async_entries(), manager.async_entries()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue