Index config entries by id (#48199)

This commit is contained in:
J. Nick Koston 2021-03-21 18:44:29 -10:00 committed by GitHub
parent 6fab4a2c82
commit 3f2ca16ad7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 74 deletions

View file

@ -619,7 +619,7 @@ class ConfigEntries:
self.flow = ConfigEntriesFlowManager(hass, self, hass_config)
self.options = OptionsFlowManager(hass)
self._hass_config = hass_config
self._entries: list[ConfigEntry] = []
self._entries: dict[str, ConfigEntry] = {}
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
EntityRegistryDisabledHandler(hass).async_setup()
@ -629,7 +629,7 @@ class ConfigEntries:
seen: set[str] = set()
result = []
for entry in self._entries:
for entry in self._entries.values():
if entry.domain not in seen:
seen.add(entry.domain)
result.append(entry.domain)
@ -639,21 +639,22 @@ class ConfigEntries:
@callback
def async_get_entry(self, entry_id: str) -> ConfigEntry | None:
"""Return entry with matching entry_id."""
for entry in self._entries:
if entry_id == entry.entry_id:
return entry
return None
return self._entries.get(entry_id)
@callback
def async_entries(self, domain: str | None = None) -> list[ConfigEntry]:
"""Return all entries or entries for a specific domain."""
if domain is None:
return list(self._entries)
return [entry for entry in self._entries if entry.domain == domain]
return list(self._entries.values())
return [entry for entry in self._entries.values() if entry.domain == domain]
async def async_add(self, entry: ConfigEntry) -> None:
"""Add and setup an entry."""
self._entries.append(entry)
if entry.entry_id in self._entries:
raise HomeAssistantError(
f"An entry with the id {entry.entry_id} already exists."
)
self._entries[entry.entry_id] = entry
await self.async_setup(entry.entry_id)
self._async_schedule_save()
@ -671,7 +672,7 @@ class ConfigEntries:
await entry.async_remove(self.hass)
self._entries.remove(entry)
del self._entries[entry.entry_id]
self._async_schedule_save()
dev_reg, ent_reg = await asyncio.gather(
@ -707,11 +708,11 @@ class ConfigEntries:
)
if config is None:
self._entries = []
self._entries = {}
return
self._entries = [
ConfigEntry(
self._entries = {
entry["entry_id"]: ConfigEntry(
version=entry["version"],
domain=entry["domain"],
entry_id=entry["entry_id"],
@ -730,7 +731,7 @@ class ConfigEntries:
disabled_by=entry.get("disabled_by"),
)
for entry in config["entries"]
]
}
async def async_setup(self, entry_id: str) -> bool:
"""Set up a config entry.
@ -920,7 +921,7 @@ class ConfigEntries:
@callback
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return data to save."""
return {"entries": [entry.as_dict() for entry in self._entries]}
return {"entries": [entry.as_dict() for entry in self._entries.values()]}
async def _old_conf_migrator(old_config: dict[str, Any]) -> dict[str, Any]:

View file

@ -18,7 +18,6 @@ from time import monotonic
import types
from typing import Any, Awaitable, Collection
from unittest.mock import AsyncMock, Mock, patch
import uuid
from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401
@ -61,6 +60,7 @@ from homeassistant.setup import async_setup_component, setup_component
from homeassistant.util.async_ import run_callback_threadsafe
import homeassistant.util.dt as date_util
from homeassistant.util.unit_system import METRIC_SYSTEM
import homeassistant.util.uuid as uuid_util
import homeassistant.util.yaml.loader as yaml_loader
_LOGGER = logging.getLogger(__name__)
@ -276,7 +276,7 @@ async def async_test_home_assistant(loop, load_registries=True):
hass.config.skip_pip = True
hass.config_entries = config_entries.ConfigEntries(hass, {})
hass.config_entries._entries = []
hass.config_entries._entries = {}
hass.config_entries._store._async_ensure_stop_listener = lambda: None
# Load the registries
@ -737,7 +737,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
):
"""Initialize a mock config entry."""
kwargs = {
"entry_id": entry_id or uuid.uuid4().hex,
"entry_id": entry_id or uuid_util.random_uuid_hex(),
"domain": domain,
"data": data or {},
"system_options": system_options,
@ -756,11 +756,11 @@ class MockConfigEntry(config_entries.ConfigEntry):
def add_to_hass(self, hass):
"""Test helper to add entry to hass."""
hass.config_entries._entries.append(self)
hass.config_entries._entries[self.entry_id] = self
def add_to_manager(self, manager):
"""Test helper to add entry to entry manager."""
manager._entries.append(self)
manager._entries[self.entry_id] = self
def patch_yaml_files(files_dict, endswith=True):

View file

@ -569,7 +569,7 @@ async def test_options_flow(hass, client):
source="bla",
connection_class=core_ce.CONN_CLASS_LOCAL_POLL,
).add_to_hass(hass)
entry = hass.config_entries._entries[0]
entry = hass.config_entries.async_entries()[0]
with patch.dict(HANDLERS, {"test": TestFlow}):
url = "/api/config/config_entries/options/flow"
@ -618,7 +618,7 @@ async def test_two_step_options_flow(hass, client):
source="bla",
connection_class=core_ce.CONN_CLASS_LOCAL_POLL,
).add_to_hass(hass)
entry = hass.config_entries._entries[0]
entry = hass.config_entries.async_entries()[0]
with patch.dict(HANDLERS, {"test": TestFlow}):
url = "/api/config/config_entries/options/flow"

View file

@ -12,6 +12,7 @@ from homeassistant import config_entries
from homeassistant.components import hue
from homeassistant.components.hue import sensor_base as hue_sensor_base
from tests.common import MockConfigEntry
from tests.components.light.conftest import mock_light_profiles # noqa: F401
@ -111,13 +112,11 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None):
if hostname is None:
hostname = "mock-host"
hass.config.components.add(hue.DOMAIN)
config_entry = config_entries.ConfigEntry(
1,
hue.DOMAIN,
"Mock Title",
{"host": hostname},
"test",
config_entries.CONN_CLASS_LOCAL_POLL,
config_entry = MockConfigEntry(
domain=hue.DOMAIN,
title="Mock Title",
data={"host": hostname},
connection_class=config_entries.CONN_CLASS_LOCAL_POLL,
system_options={},
)
mock_bridge.config_entry = config_entry
@ -125,7 +124,7 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None):
await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor")
await hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
# simulate a full setup by manually adding the bridge config entry
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
# and make sure it completes before going further
await hass.async_block_till_done()

View file

@ -9,12 +9,12 @@ from homeassistant.config_entries import (
ENTRY_STATE_LOADED,
ENTRY_STATE_NOT_LOADED,
ENTRY_STATE_SETUP_ERROR,
ConfigEntry,
)
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
from tests.components.huisbaasje.test_data import MOCK_CURRENT_MEASUREMENTS
@ -36,20 +36,20 @@ async def test_setup_entry(hass: HomeAssistant):
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
config_entry = MockConfigEntry(
version=1,
domain=huisbaasje.DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
source="test",
connection_class=CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -77,20 +77,20 @@ async def test_setup_entry_error(hass: HomeAssistant):
"huisbaasje.Huisbaasje.authenticate", side_effect=HuisbaasjeException
) as mock_authenticate:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
config_entry = MockConfigEntry(
version=1,
domain=huisbaasje.DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
source="test",
connection_class=CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
await hass.config_entries.async_setup(config_entry.entry_id)
@ -119,20 +119,20 @@ async def test_unload_entry(hass: HomeAssistant):
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
config_entry = MockConfigEntry(
version=1,
domain=huisbaasje.DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
source="test",
connection_class=CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
# Load config entry
assert await hass.config_entries.async_setup(config_entry.entry_id)

View file

@ -2,10 +2,11 @@
from unittest.mock import patch
from homeassistant.components import huisbaasje
from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigEntry
from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
from tests.components.huisbaasje.test_data import (
MOCK_CURRENT_MEASUREMENTS,
MOCK_LIMITED_CURRENT_MEASUREMENTS,
@ -24,20 +25,20 @@ async def test_setup_entry(hass: HomeAssistant):
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
config_entry = MockConfigEntry(
version=1,
domain=huisbaasje.DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
source="test",
connection_class=CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@ -81,20 +82,20 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant):
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
config_entry = MockConfigEntry(
version=1,
domain=huisbaasje.DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
source="test",
connection_class=CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View file

@ -7,7 +7,7 @@ import pytest
from homeassistant import config_entries, data_entry_flow, loader
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import dt
@ -44,7 +44,7 @@ def mock_handlers():
def manager(hass):
"""Fixture of a loaded config manager."""
manager = config_entries.ConfigEntries(hass, {})
manager._entries = []
manager._entries = {}
manager._store._async_ensure_stop_listener = lambda: None
hass.config_entries = manager
return manager
@ -1383,6 +1383,37 @@ async def test_unique_id_existing_entry(hass, manager):
assert len(async_remove_entry.mock_calls) == 1
async def test_entry_id_existing_entry(hass, manager):
"""Test that we throw when the entry id collides."""
collide_entry_id = "collide"
hass.config.components.add("comp")
MockConfigEntry(
entry_id=collide_entry_id,
domain="comp",
state=config_entries.ENTRY_STATE_LOADED,
unique_id="mock-unique-id",
).add_to_hass(hass)
class TestFlow(config_entries.ConfigFlow):
"""Test flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Test user step."""
return self.async_create_entry(title="mock-title", data={"via": "flow"})
with pytest.raises(HomeAssistantError), patch.dict(
config_entries.HANDLERS, {"comp": TestFlow}
), patch(
"homeassistant.config_entries.uuid_util.random_uuid_hex",
return_value=collide_entry_id,
):
await manager.flow.async_init(
"comp", context={"source": config_entries.SOURCE_USER}
)
async def test_unique_id_update_existing_entry_without_reload(hass, manager):
"""Test that we update an entry if there already is an entry with unique ID."""
hass.config.components.add("comp")