diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index f7e1ce5bc58..53c65a6ab07 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -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 diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 4014c2162dd..bb38c36090a 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -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 " diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 84b16817ca8..6edbecf055d 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -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): diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index e52123297ab..b3c5ecd1bf5 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -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() diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 1539fa076e4..a1ea4f98c85 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -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, diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index f2bfef960cd..402fdbd0715 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -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 diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 3e6f2407d17..08aa52e3a13 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -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} diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 9a7e06738db..ce97c7944c6 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -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) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 1cec1e75fe9..945bd3865c3 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -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: diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index cf5a3b2785a..83e1337b079 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -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) diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index c678bee5e32..3cb45182399 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -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"]) diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py index 8a81d137672..980994f3fb2 100644 --- a/tests/components/met/test_config_flow.py +++ b/tests/components/met/test_config_flow.py @@ -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 = { diff --git a/tests/components/mikrotik/test_hub.py b/tests/components/mikrotik/test_hub.py index fc37c9113ae..9a2f75f7015 100644 --- a/tests/components/mikrotik/test_hub.py +++ b/tests/components/mikrotik/test_hub.py @@ -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 diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 91ee8a7205f..8bca98f78f3 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -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): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 0c9d889d558..ae80316676a 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -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): diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index da3fb740694..17494b6b110 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -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()