208 files
This commit is contained in:
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -8,7 +8,10 @@ from homeassistant.components import script
|
||||
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
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
# 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)
|
||||
# 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
|
||||
)
|
||||
|
||||
return async_filter_known_area_ids(
|
||||
self.hass,
|
||||
|
||||
@@ -8,7 +8,10 @@ from homeassistant.components import script
|
||||
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 extract_targets_from_config
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from ....template_extraction import extract_device_ids_from_config
|
||||
|
||||
@@ -46,6 +49,14 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
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))
|
||||
# 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)
|
||||
),
|
||||
)
|
||||
|
||||
return async_filter_known_device_ids(
|
||||
self.hass,
|
||||
|
||||
@@ -8,7 +8,9 @@ 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 ....entity_filtering import async_get_all_entity_ids, async_get_all_services
|
||||
from ....reference_extraction import without_disabled_steps
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from ....template_extraction import (
|
||||
async_extract_entities_from_config,
|
||||
@@ -19,37 +21,6 @@ if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
def extract_entities_from_trigger_config(config: dict[str, Any] | list) -> set[str]:
|
||||
"""Extract entity IDs from a trigger config."""
|
||||
entities = set()
|
||||
|
||||
if not config:
|
||||
return entities
|
||||
|
||||
if isinstance(config, list):
|
||||
for item in config:
|
||||
entities.update(extract_entities_from_trigger_config(item))
|
||||
return entities
|
||||
|
||||
if not isinstance(config, dict):
|
||||
return entities
|
||||
|
||||
# Extract entity_id from trigger config
|
||||
if "entity_id" in config:
|
||||
entity_id = config["entity_id"]
|
||||
if isinstance(entity_id, str):
|
||||
entities.add(entity_id)
|
||||
elif isinstance(entity_id, list):
|
||||
entities.update([e for e in entity_id if isinstance(e, str)])
|
||||
|
||||
# Recursively process nested configs
|
||||
for value in config.values():
|
||||
if isinstance(value, (dict, list)):
|
||||
entities.update(extract_entities_from_trigger_config(value))
|
||||
|
||||
return entities
|
||||
|
||||
|
||||
def extract_referenced_entities_from_script(entity: script.ScriptEntity) -> set[str]:
|
||||
"""Return entity references from a script entity."""
|
||||
try:
|
||||
@@ -74,18 +45,12 @@ async def extract_template_entities_from_script_entity(
|
||||
``known_services`` is built once per inspection and handed down, because
|
||||
building it flattens every service Home Assistant has and every script
|
||||
with a template in it needs the same answer.
|
||||
"""
|
||||
# Get the script configuration
|
||||
config = None
|
||||
if hasattr(entity, "script"):
|
||||
# Try to get configuration safely
|
||||
if hasattr(entity.script, "config"):
|
||||
config = entity.script.config
|
||||
elif hasattr(entity.script, "_config"):
|
||||
# Fallback to _config if needed
|
||||
config = getattr(entity.script, "_config", None)
|
||||
|
||||
if not config:
|
||||
Read from the configuration as written, like the automation repair does.
|
||||
The script helper underneath keeps no configuration of its own, so this
|
||||
used to find nothing at all.
|
||||
"""
|
||||
if not (config := getattr(entity, "raw_config", None)):
|
||||
return set()
|
||||
|
||||
return await async_extract_entities_from_config(hass, config, known_services)
|
||||
@@ -112,34 +77,6 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
_known_entity_ids: set[str]
|
||||
_known_services: set[str]
|
||||
|
||||
def _get_blueprint_trigger_entities(self, entity: script.ScriptEntity) -> set[str]:
|
||||
"""Extract entity references from blueprint trigger inputs."""
|
||||
entities = set()
|
||||
|
||||
if (
|
||||
not hasattr(entity, "referenced_blueprint")
|
||||
or not entity.referenced_blueprint
|
||||
):
|
||||
return entities
|
||||
|
||||
config = getattr(entity, "_config", None)
|
||||
if not config or not isinstance(config, dict) or "use_blueprint" not in config:
|
||||
return entities
|
||||
|
||||
blueprint_config = config["use_blueprint"]
|
||||
if "input" not in blueprint_config:
|
||||
return entities
|
||||
|
||||
input_config = blueprint_config["input"]
|
||||
# Look for inputs that might contain triggers (like discard_when)
|
||||
for value in input_config.values():
|
||||
if isinstance(value, (dict, list)) and "trigger" in str(value):
|
||||
trigger_entities = extract_entities_from_trigger_config(value)
|
||||
if trigger_entities:
|
||||
entities.update(trigger_entities)
|
||||
|
||||
return entities
|
||||
|
||||
async def _async_setup_inspection(self) -> None:
|
||||
"""Cache what every script in this cycle needs looked up.
|
||||
|
||||
@@ -152,13 +89,42 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
)
|
||||
self._known_services = async_get_all_services(self.hass)
|
||||
|
||||
async def _async_named_in(self, config: dict[str, Any]) -> 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.
|
||||
"""
|
||||
steps = config.get("sequence") or []
|
||||
named = await async_extract_entities_from_action_config(
|
||||
self.hass,
|
||||
# A single step can be written without a list.
|
||||
[steps] if isinstance(steps, dict) else steps,
|
||||
known_services=self._known_services,
|
||||
)
|
||||
named |= await async_extract_entities_from_config(
|
||||
self.hass, config, self._known_services
|
||||
)
|
||||
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)
|
||||
|
||||
# Check for blueprint trigger inputs
|
||||
all_entities.update(self._get_blueprint_trigger_entities(entity))
|
||||
# Home Assistant's own list leaves out entities handed over as action
|
||||
# data, like `entity: light.kitchen` in a call to another script. The
|
||||
# automation repair reads those from the configuration as written, and
|
||||
# so does this one.
|
||||
if isinstance(raw_config := getattr(entity, "raw_config", None), dict):
|
||||
all_entities.update(
|
||||
await async_extract_entities_from_action_config(
|
||||
self.hass,
|
||||
raw_config.get("sequence") or [],
|
||||
known_services=self._known_services,
|
||||
)
|
||||
)
|
||||
|
||||
# Extract entities from Template objects within the script entity
|
||||
all_entities.update(
|
||||
@@ -167,6 +133,15 @@ 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(
|
||||
self.hass,
|
||||
entity_ids=all_entities,
|
||||
|
||||
@@ -8,7 +8,10 @@ from homeassistant.components import script
|
||||
from homeassistant.helpers import 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
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
# 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)
|
||||
# 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
|
||||
)
|
||||
|
||||
return async_filter_known_floor_ids(
|
||||
self.hass,
|
||||
|
||||
@@ -8,7 +8,10 @@ from homeassistant.components import script
|
||||
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
|
||||
from ....reference_extraction import (
|
||||
extract_targets_from_config,
|
||||
only_in_disabled_steps,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
# 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)
|
||||
# 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
|
||||
)
|
||||
|
||||
return async_filter_known_label_ids(
|
||||
self.hass,
|
||||
|
||||
Reference in New Issue
Block a user