From 4e32bf2ac910e38695a1b9bf9b610c717db97998 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 13 Sep 2022 04:28:05 +0200 Subject: [PATCH] Drop old migration code from entity registry (#78278) --- homeassistant/helpers/entity_registry.py | 159 +++++++++-------------- tests/helpers/test_entity_registry.py | 98 +------------- 2 files changed, 61 insertions(+), 196 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 701b0816d1cc..f40c6347af77 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -42,7 +42,6 @@ from homeassistant.core import ( from homeassistant.exceptions import MaxLengthExceeded from homeassistant.loader import bind_hass from homeassistant.util import slugify, uuid as uuid_util -from homeassistant.util.yaml import load_yaml from . import device_registry as dr, storage from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED @@ -56,7 +55,6 @@ if TYPE_CHECKING: T = TypeVar("T") -PATH_REGISTRY = "entity_registry.yaml" DATA_REGISTRY = "entity_registry" EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated" SAVE_DELAY = 10 @@ -177,7 +175,65 @@ class EntityRegistryStore(storage.Store): self, old_major_version: int, old_minor_version: int, old_data: dict ) -> dict: """Migrate to the new version.""" - return await _async_migrate(old_major_version, old_minor_version, old_data) + data = old_data + if old_major_version == 1 and old_minor_version < 2: + # From version 1.1 + for entity in data["entities"]: + # Populate all keys + entity["area_id"] = entity.get("area_id") + entity["capabilities"] = entity.get("capabilities") or {} + entity["config_entry_id"] = entity.get("config_entry_id") + entity["device_class"] = entity.get("device_class") + entity["device_id"] = entity.get("device_id") + entity["disabled_by"] = entity.get("disabled_by") + entity["entity_category"] = entity.get("entity_category") + entity["icon"] = entity.get("icon") + entity["name"] = entity.get("name") + entity["original_icon"] = entity.get("original_icon") + entity["original_name"] = entity.get("original_name") + entity["platform"] = entity["platform"] + entity["supported_features"] = entity.get("supported_features", 0) + entity["unit_of_measurement"] = entity.get("unit_of_measurement") + + if old_major_version == 1 and old_minor_version < 3: + # Version 1.3 adds original_device_class + for entity in data["entities"]: + # Move device_class to original_device_class + entity["original_device_class"] = entity["device_class"] + entity["device_class"] = None + + if old_major_version == 1 and old_minor_version < 4: + # Version 1.4 adds id + for entity in data["entities"]: + entity["id"] = uuid_util.random_uuid_hex() + + if old_major_version == 1 and old_minor_version < 5: + # Version 1.5 adds entity options + for entity in data["entities"]: + entity["options"] = {} + + if old_major_version == 1 and old_minor_version < 6: + # Version 1.6 adds hidden_by + for entity in data["entities"]: + entity["hidden_by"] = None + + if old_major_version == 1 and old_minor_version < 7: + # Version 1.7 adds has_entity_name + for entity in data["entities"]: + entity["has_entity_name"] = False + + if old_major_version == 1 and old_minor_version < 8: + # Cleanup after frontend bug which incorrectly updated device_class + # Fixed by frontend PR #13551 + for entity in data["entities"]: + domain = split_entity_id(entity["entity_id"])[0] + if domain in [Platform.BINARY_SENSOR, Platform.COVER]: + continue + entity["device_class"] = None + + if old_major_version > 1: + raise NotImplementedError + return data class EntityRegistryItems(UserDict[str, "RegistryEntry"]): @@ -362,13 +418,6 @@ class EntityRegistry: original_name=original_name, supported_features=supported_features, unit_of_measurement=unit_of_measurement, - # When we changed our slugify algorithm, we invalidated some - # stored entity IDs with either a __ or ending in _. - # Fix introduced in 0.86 (Jan 23, 2019). Next line can be - # removed when we release 1.0 or in 2020. - new_entity_id=".".join( - slugify(part) for part in entity_id.split(".", 1) - ), ) entity_id = self.async_generate_entity_id( @@ -722,25 +771,13 @@ class EntityRegistry: """Load the entity registry.""" async_setup_entity_restore(self.hass, self) - data = await storage.async_migrator( - self.hass, - self.hass.config.path(PATH_REGISTRY), - self._store, - old_conf_load_func=load_yaml, - old_conf_migrate_func=_async_migrate_yaml_to_json, - ) + data = await self._store.async_load() entities = EntityRegistryItems() from .entity import EntityCategory # pylint: disable=import-outside-toplevel if data is not None: for entity in data["entities"]: - # Some old installations can have some bad entities. - # Filter them out as they cause errors down the line. - # Can be removed in Jan 2021 - if not valid_entity_id(entity["entity_id"]): - continue - # We removed this in 2022.5. Remove this check in 2023.1. if entity["entity_category"] == "system": entity["entity_category"] = None @@ -922,82 +959,6 @@ def async_config_entry_disabled_by_changed( ) -async def _async_migrate( - old_major_version: int, old_minor_version: int, data: dict -) -> dict: - """Migrate to the new version.""" - if old_major_version == 1 and old_minor_version < 2: - # From version 1.1 - for entity in data["entities"]: - # Populate all keys - entity["area_id"] = entity.get("area_id") - entity["capabilities"] = entity.get("capabilities") or {} - entity["config_entry_id"] = entity.get("config_entry_id") - entity["device_class"] = entity.get("device_class") - entity["device_id"] = entity.get("device_id") - entity["disabled_by"] = entity.get("disabled_by") - entity["entity_category"] = entity.get("entity_category") - entity["icon"] = entity.get("icon") - entity["name"] = entity.get("name") - entity["original_icon"] = entity.get("original_icon") - entity["original_name"] = entity.get("original_name") - entity["platform"] = entity["platform"] - entity["supported_features"] = entity.get("supported_features", 0) - entity["unit_of_measurement"] = entity.get("unit_of_measurement") - - if old_major_version == 1 and old_minor_version < 3: - # Version 1.3 adds original_device_class - for entity in data["entities"]: - # Move device_class to original_device_class - entity["original_device_class"] = entity["device_class"] - entity["device_class"] = None - - if old_major_version == 1 and old_minor_version < 4: - # Version 1.4 adds id - for entity in data["entities"]: - entity["id"] = uuid_util.random_uuid_hex() - - if old_major_version == 1 and old_minor_version < 5: - # Version 1.5 adds entity options - for entity in data["entities"]: - entity["options"] = {} - - if old_major_version == 1 and old_minor_version < 6: - # Version 1.6 adds hidden_by - for entity in data["entities"]: - entity["hidden_by"] = None - - if old_major_version == 1 and old_minor_version < 7: - # Version 1.7 adds has_entity_name - for entity in data["entities"]: - entity["has_entity_name"] = False - - if old_major_version == 1 and old_minor_version < 8: - # Cleanup after frontend bug which incorrectly updated device_class - # Fixed by frontend PR #13551 - for entity in data["entities"]: - domain = split_entity_id(entity["entity_id"])[0] - if domain in [Platform.BINARY_SENSOR, Platform.COVER]: - continue - entity["device_class"] = None - - if old_major_version > 1: - raise NotImplementedError - return data - - -async def _async_migrate_yaml_to_json( - entities: dict[str, Any] -) -> dict[str, list[dict[str, Any]]]: - """Migrate the YAML config file to storage helper format.""" - entities_1_1 = { - "entities": [ - {"entity_id": entity_id, **info} for entity_id, info in entities.items() - ] - } - return await _async_migrate(1, 1, entities_1_1) - - @callback def async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) -> None: """Set up the entity restore mechanism.""" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 8ccba2c7eccb..64579c257665 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE -from homeassistant.core import CoreState, callback, valid_entity_id +from homeassistant.core import CoreState, callback from homeassistant.exceptions import MaxLengthExceeded from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory @@ -342,13 +342,6 @@ async def test_filter_on_load(hass, hass_storage): "unique_id": "disabled-hass", "disabled_by": "hass", # We store the string representation }, - # This entry should not be loaded because the entity_id is invalid - { - "entity_id": "test.invalid__entity", - "platform": "super_platform", - "unique_id": "invalid-hass", - "disabled_by": "hass", # We store the string representation - }, # This entry should have the entity_category reset to None { "entity_id": "test.system_entity", @@ -465,38 +458,6 @@ async def test_removing_area_id(registry): assert entry_w_area != entry_wo_area -@pytest.mark.parametrize("load_registries", [False]) -async def test_migration_yaml_to_json(hass): - """Test migration from old (yaml) data to new.""" - mock_config = MockConfigEntry(domain="test-platform", entry_id="test-config-id") - - old_conf = { - "light.kitchen": { - "config_entry_id": "test-config-id", - "unique_id": "test-unique", - "platform": "test-platform", - "name": "Test Name", - "disabled_by": er.RegistryEntryDisabler.HASS, - } - } - with patch("os.path.isfile", return_value=True), patch("os.remove"), patch( - "homeassistant.helpers.entity_registry.load_yaml", return_value=old_conf - ): - await er.async_load(hass) - registry = er.async_get(hass) - - assert registry.async_is_registered("light.kitchen") - entry = registry.async_get_or_create( - domain="light", - platform="test-platform", - unique_id="test-unique", - config_entry=mock_config, - ) - assert entry.name == "Test Name" - assert entry.disabled_by is er.RegistryEntryDisabler.HASS - assert entry.config_entry_id == "test-config-id" - - @pytest.mark.parametrize("load_registries", [False]) async def test_migration_1_1(hass, hass_storage): """Test migration from version 1.1.""" @@ -596,63 +557,6 @@ async def test_migration_1_7(hass, hass_storage): assert entry.original_device_class == "class_by_integration" -@pytest.mark.parametrize("load_registries", [False]) -async def test_loading_invalid_entity_id(hass, hass_storage): - """Test we skip entities with invalid entity IDs.""" - hass_storage[er.STORAGE_KEY] = { - "version": er.STORAGE_VERSION_MAJOR, - "minor_version": er.STORAGE_VERSION_MINOR, - "data": { - "entities": [ - { - "entity_id": "test.invalid__middle", - "platform": "super_platform", - "unique_id": "id-invalid-middle", - "name": "registry override 1", - }, - { - "entity_id": "test.invalid_end_", - "platform": "super_platform", - "unique_id": "id-invalid-end", - "name": "registry override 2", - }, - { - "entity_id": "test._invalid_start", - "platform": "super_platform", - "unique_id": "id-invalid-start", - "name": "registry override 3", - }, - ] - }, - } - - await er.async_load(hass) - registry = er.async_get(hass) - assert len(registry.entities) == 0 - - entity_invalid_middle = registry.async_get_or_create( - "test", "super_platform", "id-invalid-middle" - ) - - assert valid_entity_id(entity_invalid_middle.entity_id) - # Check name to make sure we created a new entity - assert entity_invalid_middle.name is None - - entity_invalid_end = registry.async_get_or_create( - "test", "super_platform", "id-invalid-end" - ) - - assert valid_entity_id(entity_invalid_end.entity_id) - assert entity_invalid_end.name is None - - entity_invalid_start = registry.async_get_or_create( - "test", "super_platform", "id-invalid-start" - ) - - assert valid_entity_id(entity_invalid_start.entity_id) - assert entity_invalid_start.name is None - - async def test_update_entity_unique_id(registry): """Test entity's unique_id is updated.""" mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")