361 files
This commit is contained in:
@@ -1 +1,70 @@
|
||||
"""Spook - Your homie."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components import automation
|
||||
from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
|
||||
from homeassistant.core import Event
|
||||
|
||||
|
||||
class AbstractSpookAutomationReferencesRepair(
|
||||
AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
):
|
||||
"""Base for the repairs that look for unknown references in automations.
|
||||
|
||||
An automation somebody turned off is left alone. It does nothing while it
|
||||
is off, so nothing in it can go wrong yet, and it was often turned off
|
||||
exactly because something in it is broken: being told about that again
|
||||
and again is noise. The moment it is turned back on is the moment it
|
||||
matters, so that is when it is looked at again. #1725.
|
||||
"""
|
||||
|
||||
def _should_inspect_entity(self, entity: Any) -> bool:
|
||||
"""Look only at automations that are on.
|
||||
|
||||
One that failed to load is not off, it is broken, and some of these
|
||||
repairs exist to say exactly why. That is not somebody's choice to
|
||||
leave it alone, so it is still looked at.
|
||||
"""
|
||||
if not isinstance(entity, automation.AutomationEntity):
|
||||
return True
|
||||
return bool(entity.is_on)
|
||||
|
||||
async def async_activate(self) -> None:
|
||||
"""Look again whenever an automation is turned on or off.
|
||||
|
||||
Turned on, it is reported straight away rather than at the next look.
|
||||
Turned off, what was reported for it is cleared then too.
|
||||
"""
|
||||
await super().async_activate()
|
||||
|
||||
@callback
|
||||
def _turned_on_or_off(event_data: Mapping[str, Any]) -> bool:
|
||||
"""Return whether an automation went from off to on, or back."""
|
||||
if not event_data["entity_id"].startswith(f"{automation.DOMAIN}."):
|
||||
return False
|
||||
if (old := event_data["old_state"]) is None or (
|
||||
new := event_data["new_state"]
|
||||
) is None:
|
||||
return False
|
||||
# Every run updates an automation's attributes, and those are not
|
||||
# a reason to look; only the switch between on and off is.
|
||||
return {old.state, new.state} == {STATE_ON, STATE_OFF}
|
||||
|
||||
async def _look_again(_: Event) -> None:
|
||||
await self.inspect_debouncer.async_call()
|
||||
|
||||
self._event_subs.add(
|
||||
self.hass.bus.async_listen(
|
||||
EVENT_STATE_CHANGED, _look_again, event_filter=_turned_on_or_off
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,13 +9,13 @@ 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 ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced areas in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
|
||||
@@ -9,8 +9,8 @@ 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 ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from ....template_extraction import extract_device_ids_from_config
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
|
||||
def extract_event_data_device_ids_from_trigger_config(
|
||||
@@ -46,7 +46,7 @@ def extract_event_data_device_ids_from_trigger_config(
|
||||
return device_ids
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced devices in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
|
||||
@@ -5,8 +5,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components import automation
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_STATE_CHANGED
|
||||
from homeassistant.core import Event, callback
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from ....action_extraction import (
|
||||
@@ -14,16 +13,14 @@ from ....action_extraction import (
|
||||
async_extract_entities_from_value,
|
||||
)
|
||||
from ....entity_filtering import async_get_all_entity_ids, async_get_all_services
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from ....template_extraction import (
|
||||
KNOWN_DOMAINS,
|
||||
async_extract_entities_from_config,
|
||||
async_filter_known_entity_ids_with_templates,
|
||||
)
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
@@ -260,7 +257,7 @@ async def extract_entities_from_condition_config(
|
||||
return entities
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced entity in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
@@ -271,6 +268,7 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
}
|
||||
inspect_config_entry_changed = True
|
||||
inspect_on_reload = True
|
||||
inspect_on_entity_added_or_removed = True
|
||||
|
||||
unavailable_entity_class = automation.UnavailableAutomationEntity
|
||||
entity_label = "automation"
|
||||
@@ -281,31 +279,6 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
_known_entity_ids: set[str]
|
||||
_known_services: set[str]
|
||||
|
||||
async def async_activate(self) -> None:
|
||||
"""Activate the repair."""
|
||||
await super().async_activate()
|
||||
|
||||
@callback
|
||||
def _state_entity_changed(event_data: Mapping[str, Any]) -> bool:
|
||||
"""Return if a state entity was added or removed."""
|
||||
return (
|
||||
event_data.get("old_state") is None
|
||||
or event_data.get("new_state") is None
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_call_inspect_debouncer(_: Event) -> None:
|
||||
"""Trigger an inspection when a state entity is added or removed."""
|
||||
self.inspect_debouncer.async_schedule_call()
|
||||
|
||||
self._event_subs.add(
|
||||
self.hass.bus.async_listen(
|
||||
EVENT_STATE_CHANGED,
|
||||
_async_call_inspect_debouncer,
|
||||
event_filter=_state_entity_changed,
|
||||
),
|
||||
)
|
||||
|
||||
async def _async_setup_inspection(self) -> None:
|
||||
"""Cache what every automation in this cycle needs looked up.
|
||||
|
||||
@@ -318,10 +291,6 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
)
|
||||
self._known_services = async_get_all_services(self.hass)
|
||||
|
||||
def _should_inspect_entity(self, entity: Any) -> bool:
|
||||
"""Skip disabled automations."""
|
||||
return entity.enabled
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown entity IDs referenced by ``entity`` (incl. templates)."""
|
||||
all_entities = set(entity.referenced_entities)
|
||||
|
||||
@@ -9,13 +9,13 @@ 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 ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced floors in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
|
||||
@@ -9,13 +9,13 @@ 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 ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced labels in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
|
||||
@@ -16,13 +16,13 @@ from ....entity_filtering import (
|
||||
async_find_services_in_sequence,
|
||||
async_get_all_services,
|
||||
)
|
||||
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
|
||||
from . import AbstractSpookAutomationReferencesRepair
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
class SpookRepair(AbstractSpookAutomationReferencesRepair):
|
||||
"""Spook repair tries to find unknown referenced services in automations."""
|
||||
|
||||
domain = automation.DOMAIN
|
||||
@@ -47,10 +47,6 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
|
||||
"""Cache known services for this inspection cycle."""
|
||||
self._known_services = async_get_all_services(self.hass)
|
||||
|
||||
def _should_inspect_entity(self, entity: Any) -> bool:
|
||||
"""Skip disabled automations."""
|
||||
return entity.enabled
|
||||
|
||||
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
|
||||
"""Return unknown services called by ``entity``."""
|
||||
return async_filter_known_services(
|
||||
|
||||
Reference in New Issue
Block a user