Index config entries by id (#48199)
This commit is contained in:
parent
6fab4a2c82
commit
3f2ca16ad7
7 changed files with 106 additions and 74 deletions
|
@ -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]:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Reference in a new issue