291 files
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components import script
|
||||
@@ -9,7 +10,8 @@ from homeassistant.helpers import area_registry as ar
|
||||
|
||||
from ....entity_filtering import async_filter_known_area_ids, async_get_all_area_ids
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
async_raw_config_references,
|
||||
core_references,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
@@ -39,16 +41,17 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown area IDs referenced by ``entity``."""
|
||||
area_ids = set(entity.script.referenced_areas)
|
||||
area_ids = core_references(entity, "areas")
|
||||
|
||||
# Also walk the raw configuration; the built-in extraction misses
|
||||
# references nested in some step types, like repeat sequences.
|
||||
if raw_config := getattr(entity, "raw_config", None):
|
||||
area_ids.update(extract_targets_from_config(raw_config).area_ids)
|
||||
# Templates that look up an area that is not there are read too.
|
||||
references = partial(async_raw_config_references, self.hass, kind="areas")
|
||||
area_ids.update(references(raw_config))
|
||||
# A disabled step does nothing, so what only it names is left out.
|
||||
area_ids -= only_in_disabled_steps(
|
||||
raw_config, lambda found: extract_targets_from_config(found).area_ids
|
||||
)
|
||||
# Read the same way, or a template there still counts.
|
||||
area_ids -= only_in_disabled_steps(raw_config, references)
|
||||
|
||||
return async_filter_known_area_ids(
|
||||
self.hass,
|
||||
|
||||
@@ -9,7 +9,9 @@ from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from ....entity_filtering import async_filter_known_device_ids, async_get_all_device_ids
|
||||
from ....reference_extraction import (
|
||||
core_references,
|
||||
extract_targets_from_config,
|
||||
names_given_to_templates,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
@@ -41,20 +43,22 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown device IDs referenced by ``entity``."""
|
||||
device_ids = set(entity.script.referenced_devices)
|
||||
device_ids = core_references(entity, "devices")
|
||||
|
||||
# Also walk the raw configuration; the built-in extraction misses
|
||||
# references nested in some step types, like repeat sequences.
|
||||
if raw_config := getattr(entity, "raw_config", None):
|
||||
device_ids.update(extract_targets_from_config(raw_config).device_ids)
|
||||
# Devices referenced via device_entities() in templates.
|
||||
device_ids.update(extract_device_ids_from_config(raw_config))
|
||||
# Devices referenced through the device functions in templates.
|
||||
# The variables and fields it gives its templates hide a call.
|
||||
shadowed = names_given_to_templates(raw_config)
|
||||
device_ids.update(extract_device_ids_from_config(raw_config, shadowed))
|
||||
# A disabled step does nothing, so what only it names is left out.
|
||||
device_ids -= only_in_disabled_steps(
|
||||
raw_config,
|
||||
lambda found: (
|
||||
extract_targets_from_config(found).device_ids
|
||||
| extract_device_ids_from_config(found)
|
||||
| extract_device_ids_from_config(found, shadowed)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -8,29 +8,25 @@ from homeassistant.components import script
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from ....action_extraction import async_extract_entities_from_action_config
|
||||
from ....action_extraction import (
|
||||
async_extract_entities_from_action_config,
|
||||
async_unknown_entities_named,
|
||||
)
|
||||
from ....entity_filtering import async_get_all_entity_ids, async_get_all_services
|
||||
from ....reference_extraction import without_disabled_steps
|
||||
from ....reference_extraction import (
|
||||
core_references,
|
||||
names_given_to_templates,
|
||||
without_never_rendered,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from ....template_extraction import (
|
||||
async_extract_entities_from_config,
|
||||
async_filter_known_entity_ids_with_templates,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
def extract_referenced_entities_from_script(entity: script.ScriptEntity) -> set[str]:
|
||||
"""Return entity references from a script entity."""
|
||||
try:
|
||||
return set(entity.script.referenced_entities)
|
||||
except TypeError as err:
|
||||
if str(err) != "unhashable type: 'dict'":
|
||||
raise
|
||||
return set()
|
||||
|
||||
|
||||
async def extract_template_entities_from_script_entity(
|
||||
hass: HomeAssistant,
|
||||
entity: Any,
|
||||
@@ -53,7 +49,14 @@ async def extract_template_entities_from_script_entity(
|
||||
if not (config := getattr(entity, "raw_config", None)):
|
||||
return set()
|
||||
|
||||
return await async_extract_entities_from_config(hass, config, known_services)
|
||||
# A description, a name or a field example is shown, never rendered: an
|
||||
# entity in an example template there is not one the script uses.
|
||||
return await async_extract_entities_from_config(
|
||||
hass,
|
||||
without_never_rendered(config),
|
||||
known_services,
|
||||
names_given_to_templates(config),
|
||||
)
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
@@ -89,12 +92,15 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
)
|
||||
self._known_services = async_get_all_services(self.hass)
|
||||
|
||||
async def _async_named_in(self, config: dict[str, Any]) -> set[str]:
|
||||
async def _async_named_in(
|
||||
self, config: dict[str, Any], shadowed: frozenset[str]
|
||||
) -> set[str]:
|
||||
"""Return the entities a configuration names, the way this repair reads it.
|
||||
|
||||
The same reading the report is built from, the steps and templates
|
||||
alike, so comparing it with and without the disabled steps leaves out
|
||||
exactly what only those name.
|
||||
exactly what only those name. ``shadowed`` comes from the whole
|
||||
configuration, as `config` can be one without its disabled steps.
|
||||
"""
|
||||
steps = config.get("sequence") or []
|
||||
named = await async_extract_entities_from_action_config(
|
||||
@@ -102,16 +108,20 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
# A single step can be written without a list.
|
||||
[steps] if isinstance(steps, dict) else steps,
|
||||
known_services=self._known_services,
|
||||
shadowed=shadowed,
|
||||
)
|
||||
named |= await async_extract_entities_from_config(
|
||||
self.hass, config, self._known_services
|
||||
self.hass,
|
||||
without_never_rendered(config),
|
||||
self._known_services,
|
||||
shadowed,
|
||||
)
|
||||
return named
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown entity IDs referenced by ``entity`` (incl. templates)."""
|
||||
# Get all referenced entities from the script
|
||||
all_entities = extract_referenced_entities_from_script(entity)
|
||||
all_entities = core_references(entity, "entities")
|
||||
|
||||
# Home Assistant's own list leaves out entities handed over as action
|
||||
# data, like `entity: light.kitchen` in a call to another script. The
|
||||
@@ -123,6 +133,7 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
self.hass,
|
||||
raw_config.get("sequence") or [],
|
||||
known_services=self._known_services,
|
||||
shadowed=names_given_to_templates(raw_config),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -133,18 +144,11 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
)
|
||||
)
|
||||
|
||||
# Home Assistant's own list includes disabled steps too. A step parked
|
||||
# that way does nothing, so what only it names is left out: whatever
|
||||
# this repair finds in the configuration, and no longer finds once
|
||||
# those are pruned.
|
||||
if isinstance(raw_config, dict):
|
||||
named = await self._async_named_in(raw_config)
|
||||
still_named = await self._async_named_in(without_disabled_steps(raw_config))
|
||||
all_entities -= named - still_named
|
||||
|
||||
return await async_filter_known_entity_ids_with_templates(
|
||||
return await async_unknown_entities_named(
|
||||
self.hass,
|
||||
entity_ids=all_entities,
|
||||
all_entities,
|
||||
raw_config,
|
||||
named_in=self._async_named_in,
|
||||
known_entity_ids=self._known_entity_ids,
|
||||
known_services=self._known_services,
|
||||
)
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components import script
|
||||
from homeassistant.helpers import floor_registry as fr
|
||||
from homeassistant.helpers import area_registry as ar, floor_registry as fr
|
||||
|
||||
from ....entity_filtering import async_filter_known_floor_ids, async_get_all_floor_ids
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
async_raw_config_references,
|
||||
core_references,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
@@ -23,7 +25,12 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
|
||||
domain = script.DOMAIN
|
||||
repair = "script_unknown_floor_references"
|
||||
inspect_events = {fr.EVENT_FLOOR_REGISTRY_UPDATED}
|
||||
# Templates look floors up through areas as well, so a renamed area
|
||||
# can change what they find.
|
||||
inspect_events = {
|
||||
ar.EVENT_AREA_REGISTRY_UPDATED,
|
||||
fr.EVENT_FLOOR_REGISTRY_UPDATED,
|
||||
}
|
||||
inspect_on_reload = True
|
||||
|
||||
unavailable_entity_class = script.UnavailableScriptEntity
|
||||
@@ -39,16 +46,17 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown floor IDs referenced by ``entity``."""
|
||||
floor_ids = set(entity.script.referenced_floors)
|
||||
floor_ids = core_references(entity, "floors")
|
||||
|
||||
# Also walk the raw configuration; the built-in extraction misses
|
||||
# references nested in some step types, like repeat sequences.
|
||||
if raw_config := getattr(entity, "raw_config", None):
|
||||
floor_ids.update(extract_targets_from_config(raw_config).floor_ids)
|
||||
# Templates that look up a floor that is not there are read too.
|
||||
references = partial(async_raw_config_references, self.hass, kind="floors")
|
||||
floor_ids.update(references(raw_config))
|
||||
# A disabled step does nothing, so what only it names is left out.
|
||||
floor_ids -= only_in_disabled_steps(
|
||||
raw_config, lambda found: extract_targets_from_config(found).floor_ids
|
||||
)
|
||||
# Read the same way, or a template there still counts.
|
||||
floor_ids -= only_in_disabled_steps(raw_config, references)
|
||||
|
||||
return async_filter_known_floor_ids(
|
||||
self.hass,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components import script
|
||||
@@ -9,7 +10,8 @@ from homeassistant.helpers import label_registry as lr
|
||||
|
||||
from ....entity_filtering import async_filter_known_label_ids, async_get_all_label_ids
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
async_raw_config_references,
|
||||
core_references,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
@@ -39,16 +41,17 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown label IDs referenced by ``entity``."""
|
||||
label_ids = set(entity.script.referenced_labels)
|
||||
label_ids = core_references(entity, "labels")
|
||||
|
||||
# Also walk the raw configuration; the built-in extraction misses
|
||||
# references nested in some step types, like repeat sequences.
|
||||
if raw_config := getattr(entity, "raw_config", None):
|
||||
label_ids.update(extract_targets_from_config(raw_config).label_ids)
|
||||
# Templates that look up a label that is not there are read too.
|
||||
references = partial(async_raw_config_references, self.hass, kind="labels")
|
||||
label_ids.update(references(raw_config))
|
||||
# A disabled step does nothing, so what only it names is left out.
|
||||
label_ids -= only_in_disabled_steps(
|
||||
raw_config, lambda found: extract_targets_from_config(found).label_ids
|
||||
)
|
||||
# Read the same way, or a template there still counts.
|
||||
label_ids -= only_in_disabled_steps(raw_config, references)
|
||||
|
||||
return async_filter_known_label_ids(
|
||||
self.hass,
|
||||
|
||||
Reference in New Issue
Block a user