Freeze config entry data (#32615)

* Freeze config entry data

* Fix mutating entry.data

* Fix config entry options tests
This commit is contained in:
Paulus Schoutsen 2020-03-09 14:07:50 -07:00 committed by GitHub
parent 3318e65948
commit d4615fd432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 71 additions and 45 deletions

View File

@ -52,9 +52,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
# Check if host needs to be updated
entry = entries[0]
if entry.data[CONF_HOST] != host:
entry.data[CONF_HOST] = host
entry.title = format_title(host)
hass.config_entries.async_update_entry(entry)
hass.config_entries.async_update_entry(
entry, title=format_title(host), data={**entry.data, CONF_HOST: host}
)
return True

View File

@ -568,7 +568,7 @@ async def async_setup_entry(hass, entry):
# If user didn't have configuration.yaml config, generate defaults
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):
_LOGGER.warning(
"Data in your configuration entry is going to override your "

View File

@ -227,7 +227,7 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry):
"""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]
async def async_step_init(self, user_input=None):

View File

@ -158,13 +158,14 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if self._id.startswith("uuid:"):
self._id = self._id[5:]
config_entry = await self.async_set_unique_id(ip_address)
if config_entry:
config_entry.data[CONF_ID] = self._id
config_entry.data[CONF_MANUFACTURER] = self._manufacturer
config_entry.data[CONF_MODEL] = self._model
self.hass.config_entries.async_update_entry(config_entry)
return self.async_abort(reason="already_configured")
await self.async_set_unique_id(ip_address)
self._abort_if_unique_id_configured(
{
CONF_ID: self._id,
CONF_MANUFACTURER: self._manufacturer,
CONF_MODEL: self._model,
}
)
self.context["title_placeholders"] = {"model": self._model}
return await self.async_step_confirm()

View File

@ -109,8 +109,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
entry.data[CONF_OAUTH_CLIENT_SECRET],
entry.data[CONF_REFRESH_TOKEN],
)
entry.data[CONF_REFRESH_TOKEN] = token.refresh_token
hass.config_entries.async_update_entry(entry)
hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_REFRESH_TOKEN: token.refresh_token}
)
# Get devices and their current status
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_SECRET],
)
self._entry.data[CONF_REFRESH_TOKEN] = self._token.refresh_token
self._hass.config_entries.async_update_entry(self._entry)
self._hass.config_entries.async_update_entry(
self._entry,
data={
**self._entry.data,
CONF_REFRESH_TOKEN: self._token.refresh_token,
},
)
_LOGGER.debug(
"Regenerated refresh token for installed app: %s",
self._installed_app_id,

View File

@ -428,8 +428,9 @@ async def smartapp_update(hass: HomeAssistantType, req, resp, app):
None,
)
if entry:
entry.data[CONF_REFRESH_TOKEN] = req.refresh_token
hass.config_entries.async_update_entry(entry)
hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_REFRESH_TOKEN: req.refresh_token}
)
_LOGGER.debug(
"Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id

View File

@ -196,7 +196,7 @@ class TransmissionClient:
def add_options(self):
"""Add options for entry."""
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
)
options = {CONF_SCAN_INTERVAL: scan_interval}

View File

@ -151,9 +151,10 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
return False
# 'register'/save UDN
config_entry.data["udn"] = device.udn
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
device_registry = await dr.async_get_registry(hass)

View File

@ -2,6 +2,7 @@
import asyncio
import functools
import logging
from types import MappingProxyType
from typing import Any, Callable, Dict, List, Optional, Set, Union, cast
import uuid
import weakref
@ -139,10 +140,10 @@ class ConfigEntry:
self.title = title
# Config data
self.data = data
self.data = MappingProxyType(data)
# Entry options
self.options = options or {}
self.options = MappingProxyType(options or {})
# Entry system options
self.system_options = SystemOptions(**system_options)
@ -396,8 +397,8 @@ class ConfigEntry:
"version": self.version,
"domain": self.domain,
"title": self.title,
"data": self.data,
"options": self.options,
"data": dict(self.data),
"options": dict(self.options),
"system_options": self.system_options.as_dict(),
"source": self.source,
"connection_class": self.connection_class,
@ -720,6 +721,7 @@ class ConfigEntries:
entry: ConfigEntry,
*,
unique_id: Union[str, dict, None] = _UNDEF,
title: Union[str, dict] = _UNDEF,
data: dict = _UNDEF,
options: dict = _UNDEF,
system_options: dict = _UNDEF,
@ -728,11 +730,14 @@ class ConfigEntries:
if unique_id is not _UNDEF:
entry.unique_id = cast(Optional[str], unique_id)
if title is not _UNDEF:
entry.title = cast(str, title)
if data is not _UNDEF:
entry.data = data
entry.data = MappingProxyType(data)
if options is not _UNDEF:
entry.options = options
entry.options = MappingProxyType(options)
if system_options is not _UNDEF:
entry.system_options.update(**system_options)
@ -818,7 +823,9 @@ class ConfigFlow(data_entry_flow.FlowHandler):
raise data_entry_flow.UnknownHandler
@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."""
assert self.hass
if self.unique_id is None:

View File

@ -38,7 +38,7 @@ async def test_setup_entry(hass):
async def test_setup_entry_fails(hass):
"""Test successful setup of entry."""
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)

View File

@ -196,7 +196,7 @@ async def test_hap_with_name(hass, mock_connection, hmip_config_entry):
entity_name = f"{home_name} Treppe"
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(
hass, mock_connection, hmip_config_entry
).async_get_mock_hap(test_devices=["Treppe"])

View File

@ -73,11 +73,10 @@ async def test_flow_entry_already_exists(hass):
Test when the form should show when user puts existing location
in the config gui. Then the form should show with error.
"""
first_entry = MockConfigEntry(domain="met")
first_entry.data["name"] = "home"
first_entry.data[CONF_LONGITUDE] = 0
first_entry.data[CONF_LATITUDE] = 0
first_entry.data[CONF_ELEVATION] = 0
first_entry = MockConfigEntry(
domain="met",
data={"name": "home", CONF_LATITUDE: 0, CONF_LONGITUDE: 0, CONF_ELEVATION: 0},
)
first_entry.add_to_hass(hass)
test_data = {

View File

@ -33,10 +33,10 @@ async def setup_mikrotik_entry(hass, **kwargs):
config_entry.add_to_hass(hass)
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:
config_entry.options["arp_ping"] = True
config_entry.options = {**config_entry.options, "arp_ping": True}
with patch("librouteros.connect"), patch.object(
mikrotik.hub.MikrotikData, "command", new=mock_command

View File

@ -316,9 +316,10 @@ async def test_ssdp_already_configured(hass, remote):
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_MANUFACTURER] is None
assert result["data"][CONF_MODEL] is None
assert result["data"][CONF_ID] is None
entry = result["result"]
assert entry.data[CONF_MANUFACTURER] is None
assert entry.data[CONF_MODEL] is None
assert entry.data[CONF_ID] is None
# failed as already configured
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"
# check updated device info
assert result["data"][CONF_MANUFACTURER] == "fake_manufacturer"
assert result["data"][CONF_MODEL] == "fake_model"
assert result["data"][CONF_ID] == "fake_uuid"
assert entry.data[CONF_MANUFACTURER] == "fake_manufacturer"
assert entry.data[CONF_MODEL] == "fake_model"
assert entry.data[CONF_ID] == "fake_uuid"
async def test_autodetect_websocket(hass, remote):

View File

@ -423,7 +423,10 @@ async def test_event_handler_dispatches_updated_devices(
data={"codeId": "1"},
)
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
def signal(ids):
@ -479,7 +482,10 @@ async def test_event_handler_fires_button_events(
device.device_id, capability="button", attribute="button", value="pushed"
)
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
def handler(evt):

View File

@ -456,6 +456,8 @@ async def test_saving_and_loading(hass):
"test", context={"source": config_entries.SOURCE_USER}
)
assert len(hass.config_entries.async_entries()) == 2
# To trigger the call_later
async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1))
# To execute the save
@ -465,6 +467,8 @@ async def test_saving_and_loading(hass):
manager = config_entries.ConfigEntries(hass, {})
await manager.async_initialize()
assert len(manager.async_entries()) == 2
# Ensure same order
for orig, loaded in zip(
hass.config_entries.async_entries(), manager.async_entries()