mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
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
@ -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
|
||||
|
||||
|
@ -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 "
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"])
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user