Improve behaviour when disabling or enabling config entries (#47301)
This commit is contained in:
parent
7626aa5c94
commit
504e5b77ca
5 changed files with 97 additions and 96 deletions
|
@ -11,10 +11,9 @@ import weakref
|
|||
import attr
|
||||
|
||||
from homeassistant import data_entry_flow, loader
|
||||
from homeassistant.const import EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
from homeassistant.helpers.event import Event
|
||||
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
||||
from homeassistant.setup import async_process_deps_reqs, async_setup_component
|
||||
|
@ -807,12 +806,21 @@ class ConfigEntries:
|
|||
entry.disabled_by = disabled_by
|
||||
self._async_schedule_save()
|
||||
|
||||
# Unload the config entry, then fire an event
|
||||
dev_reg = device_registry.async_get(self.hass)
|
||||
ent_reg = entity_registry.async_get(self.hass)
|
||||
|
||||
if not entry.disabled_by:
|
||||
# The config entry will no longer be disabled, enable devices and entities
|
||||
device_registry.async_config_entry_disabled_by_changed(dev_reg, entry)
|
||||
entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry)
|
||||
|
||||
# Load or unload the config entry
|
||||
reload_result = await self.async_reload(entry_id)
|
||||
|
||||
self.hass.bus.async_fire(
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id}
|
||||
)
|
||||
if entry.disabled_by:
|
||||
# The config entry has been disabled, disable devices and entities
|
||||
device_registry.async_config_entry_disabled_by_changed(dev_reg, entry)
|
||||
entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry)
|
||||
|
||||
return reload_result
|
||||
|
||||
|
@ -1250,8 +1258,16 @@ class EntityRegistryDisabledHandler:
|
|||
|
||||
@callback
|
||||
def _handle_entry_updated_filter(event: Event) -> bool:
|
||||
"""Handle entity registry entry update filter."""
|
||||
if event.data["action"] != "update" or "disabled_by" not in event.data["changes"]:
|
||||
"""Handle entity registry entry update filter.
|
||||
|
||||
Only handle changes to "disabled_by".
|
||||
If "disabled_by" was DISABLED_CONFIG_ENTRY, reload is not needed.
|
||||
"""
|
||||
if (
|
||||
event.data["action"] != "update"
|
||||
or "disabled_by" not in event.data["changes"]
|
||||
or event.data["changes"]["disabled_by"] == entity_registry.DISABLED_CONFIG_ENTRY
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
|
|
@ -202,7 +202,6 @@ CONF_ZONE = "zone"
|
|||
# #### EVENTS ####
|
||||
EVENT_CALL_SERVICE = "call_service"
|
||||
EVENT_COMPONENT_LOADED = "component_loaded"
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED = "config_entry_disabled_by_updated"
|
||||
EVENT_CORE_CONFIG_UPDATE = "core_config_updated"
|
||||
EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close"
|
||||
EVENT_HOMEASSISTANT_START = "homeassistant_start"
|
||||
|
|
|
@ -6,10 +6,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union,
|
|||
|
||||
import attr
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
)
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import Event, callback
|
||||
from homeassistant.loader import bind_hass
|
||||
import homeassistant.util.uuid as uuid_util
|
||||
|
@ -20,6 +17,8 @@ from .typing import UNDEFINED, HomeAssistantType, UndefinedType
|
|||
# mypy: disallow_any_generics
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
from . import entity_registry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -143,10 +142,6 @@ class DeviceRegistry:
|
|||
self.hass = hass
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._clear_index()
|
||||
self.hass.bus.async_listen(
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED,
|
||||
self.async_config_entry_disabled_by_changed,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_get(self, device_id: str) -> Optional[DeviceEntry]:
|
||||
|
@ -618,38 +613,6 @@ class DeviceRegistry:
|
|||
if area_id == device.area_id:
|
||||
self._async_update_device(dev_id, area_id=None)
|
||||
|
||||
@callback
|
||||
def async_config_entry_disabled_by_changed(self, event: Event) -> None:
|
||||
"""Handle a config entry being disabled or enabled.
|
||||
|
||||
Disable devices in the registry that are associated to a config entry when
|
||||
the config entry is disabled.
|
||||
"""
|
||||
config_entry = self.hass.config_entries.async_get_entry(
|
||||
event.data["config_entry_id"]
|
||||
)
|
||||
|
||||
# The config entry may be deleted already if the event handling is late
|
||||
if not config_entry:
|
||||
return
|
||||
|
||||
if not config_entry.disabled_by:
|
||||
devices = async_entries_for_config_entry(
|
||||
self, event.data["config_entry_id"]
|
||||
)
|
||||
for device in devices:
|
||||
if device.disabled_by != DISABLED_CONFIG_ENTRY:
|
||||
continue
|
||||
self.async_update_device(device.id, disabled_by=None)
|
||||
return
|
||||
|
||||
devices = async_entries_for_config_entry(self, event.data["config_entry_id"])
|
||||
for device in devices:
|
||||
if device.disabled:
|
||||
# Entity already disabled, do not overwrite
|
||||
continue
|
||||
self.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get(hass: HomeAssistantType) -> DeviceRegistry:
|
||||
|
@ -691,6 +654,34 @@ def async_entries_for_config_entry(
|
|||
]
|
||||
|
||||
|
||||
@callback
|
||||
def async_config_entry_disabled_by_changed(
|
||||
registry: DeviceRegistry, config_entry: "ConfigEntry"
|
||||
) -> None:
|
||||
"""Handle a config entry being disabled or enabled.
|
||||
|
||||
Disable devices in the registry that are associated with a config entry when
|
||||
the config entry is disabled, enable devices in the registry that are associated
|
||||
with a config entry when the config entry is enabled and the devices are marked
|
||||
DISABLED_CONFIG_ENTRY.
|
||||
"""
|
||||
|
||||
devices = async_entries_for_config_entry(registry, config_entry.entry_id)
|
||||
|
||||
if not config_entry.disabled_by:
|
||||
for device in devices:
|
||||
if device.disabled_by != DISABLED_CONFIG_ENTRY:
|
||||
continue
|
||||
registry.async_update_device(device.id, disabled_by=None)
|
||||
return
|
||||
|
||||
for device in devices:
|
||||
if device.disabled:
|
||||
# Device already disabled, do not overwrite
|
||||
continue
|
||||
registry.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY)
|
||||
|
||||
|
||||
@callback
|
||||
def async_cleanup(
|
||||
hass: HomeAssistantType,
|
||||
|
|
|
@ -31,7 +31,6 @@ from homeassistant.const import (
|
|||
ATTR_RESTORED,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
|
@ -158,10 +157,6 @@ class EntityRegistry:
|
|||
self.hass.bus.async_listen(
|
||||
EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified
|
||||
)
|
||||
self.hass.bus.async_listen(
|
||||
EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED,
|
||||
self.async_config_entry_disabled_by_changed,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_get_device_class_lookup(self, domain_device_classes: set) -> dict:
|
||||
|
@ -363,40 +358,6 @@ class EntityRegistry:
|
|||
for entity in entities:
|
||||
self.async_update_entity(entity.entity_id, disabled_by=DISABLED_DEVICE)
|
||||
|
||||
@callback
|
||||
def async_config_entry_disabled_by_changed(self, event: Event) -> None:
|
||||
"""Handle a config entry being disabled or enabled.
|
||||
|
||||
Disable entities in the registry that are associated to a config entry when
|
||||
the config entry is disabled.
|
||||
"""
|
||||
config_entry = self.hass.config_entries.async_get_entry(
|
||||
event.data["config_entry_id"]
|
||||
)
|
||||
|
||||
# The config entry may be deleted already if the event handling is late
|
||||
if not config_entry:
|
||||
return
|
||||
|
||||
if not config_entry.disabled_by:
|
||||
entities = async_entries_for_config_entry(
|
||||
self, event.data["config_entry_id"]
|
||||
)
|
||||
for entity in entities:
|
||||
if entity.disabled_by != DISABLED_CONFIG_ENTRY:
|
||||
continue
|
||||
self.async_update_entity(entity.entity_id, disabled_by=None)
|
||||
return
|
||||
|
||||
entities = async_entries_for_config_entry(self, event.data["config_entry_id"])
|
||||
for entity in entities:
|
||||
if entity.disabled:
|
||||
# Entity already disabled, do not overwrite
|
||||
continue
|
||||
self.async_update_entity(
|
||||
entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_entity(
|
||||
self,
|
||||
|
@ -443,7 +404,8 @@ class EntityRegistry:
|
|||
"""Private facing update properties method."""
|
||||
old = self.entities[entity_id]
|
||||
|
||||
changes = {}
|
||||
new_values = {} # Dict with new key/value pairs
|
||||
old_values = {} # Dict with old key/value pairs
|
||||
|
||||
for attr_name, value in (
|
||||
("name", name),
|
||||
|
@ -460,7 +422,8 @@ class EntityRegistry:
|
|||
("original_icon", original_icon),
|
||||
):
|
||||
if value is not UNDEFINED and value != getattr(old, attr_name):
|
||||
changes[attr_name] = value
|
||||
new_values[attr_name] = value
|
||||
old_values[attr_name] = getattr(old, attr_name)
|
||||
|
||||
if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id:
|
||||
if self.async_is_registered(new_entity_id):
|
||||
|
@ -473,7 +436,8 @@ class EntityRegistry:
|
|||
raise ValueError("New entity ID should be same domain")
|
||||
|
||||
self.entities.pop(entity_id)
|
||||
entity_id = changes["entity_id"] = new_entity_id
|
||||
entity_id = new_values["entity_id"] = new_entity_id
|
||||
old_values["entity_id"] = old.entity_id
|
||||
|
||||
if new_unique_id is not UNDEFINED:
|
||||
conflict_entity_id = self.async_get_entity_id(
|
||||
|
@ -484,18 +448,19 @@ class EntityRegistry:
|
|||
f"Unique id '{new_unique_id}' is already in use by "
|
||||
f"'{conflict_entity_id}'"
|
||||
)
|
||||
changes["unique_id"] = new_unique_id
|
||||
new_values["unique_id"] = new_unique_id
|
||||
old_values["unique_id"] = old.unique_id
|
||||
|
||||
if not changes:
|
||||
if not new_values:
|
||||
return old
|
||||
|
||||
self._remove_index(old)
|
||||
new = attr.evolve(old, **changes)
|
||||
new = attr.evolve(old, **new_values)
|
||||
self._register_entry(new)
|
||||
|
||||
self.async_schedule_save()
|
||||
|
||||
data = {"action": "update", "entity_id": entity_id, "changes": list(changes)}
|
||||
data = {"action": "update", "entity_id": entity_id, "changes": old_values}
|
||||
|
||||
if old.entity_id != entity_id:
|
||||
data["old_entity_id"] = old.entity_id
|
||||
|
@ -670,6 +635,36 @@ def async_entries_for_config_entry(
|
|||
]
|
||||
|
||||
|
||||
@callback
|
||||
def async_config_entry_disabled_by_changed(
|
||||
registry: EntityRegistry, config_entry: "ConfigEntry"
|
||||
) -> None:
|
||||
"""Handle a config entry being disabled or enabled.
|
||||
|
||||
Disable entities in the registry that are associated with a config entry when
|
||||
the config entry is disabled, enable entities in the registry that are associated
|
||||
with a config entry when the config entry is enabled and the entities are marked
|
||||
DISABLED_CONFIG_ENTRY.
|
||||
"""
|
||||
|
||||
entities = async_entries_for_config_entry(registry, config_entry.entry_id)
|
||||
|
||||
if not config_entry.disabled_by:
|
||||
for entity in entities:
|
||||
if entity.disabled_by != DISABLED_CONFIG_ENTRY:
|
||||
continue
|
||||
registry.async_update_entity(entity.entity_id, disabled_by=None)
|
||||
return
|
||||
|
||||
for entity in entities:
|
||||
if entity.disabled:
|
||||
# Entity already disabled, do not overwrite
|
||||
continue
|
||||
registry.async_update_entity(
|
||||
entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY
|
||||
)
|
||||
|
||||
|
||||
async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""Migrate the YAML config file to storage helper format."""
|
||||
return {
|
||||
|
|
|
@ -313,7 +313,7 @@ async def test_updating_config_entry_id(hass, registry, update_events):
|
|||
assert update_events[0]["entity_id"] == entry.entity_id
|
||||
assert update_events[1]["action"] == "update"
|
||||
assert update_events[1]["entity_id"] == entry.entity_id
|
||||
assert update_events[1]["changes"] == ["config_entry_id"]
|
||||
assert update_events[1]["changes"] == {"config_entry_id": "mock-id-1"}
|
||||
|
||||
|
||||
async def test_removing_config_entry_id(hass, registry, update_events):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue