mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Allow disabling specific triggers/actions/conditions (#70082)
This commit is contained in:
parent
ae9315aa29
commit
e04fef3c2d
@ -143,6 +143,7 @@ CONF_EFFECT: Final = "effect"
|
||||
CONF_ELEVATION: Final = "elevation"
|
||||
CONF_ELSE: Final = "else"
|
||||
CONF_EMAIL: Final = "email"
|
||||
CONF_ENABLED: Final = "enabled"
|
||||
CONF_ENTITIES: Final = "entities"
|
||||
CONF_ENTITY_CATEGORY: Final = "entity_category"
|
||||
CONF_ENTITY_ID: Final = "entity_id"
|
||||
|
@ -27,6 +27,7 @@ from homeassistant.const import (
|
||||
CONF_BELOW,
|
||||
CONF_CONDITION,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_ENABLED,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ID,
|
||||
CONF_MATCH,
|
||||
@ -166,6 +167,18 @@ async def async_from_config(
|
||||
if factory is None:
|
||||
raise HomeAssistantError(f'Invalid condition "{condition}" specified {config}')
|
||||
|
||||
# Check if condition is not enabled
|
||||
if not config.get(CONF_ENABLED, True):
|
||||
|
||||
@trace_condition_function
|
||||
def disabled_condition(
|
||||
hass: HomeAssistant, variables: TemplateVarsType = None
|
||||
) -> bool:
|
||||
"""Condition not enabled, will always pass."""
|
||||
return True
|
||||
|
||||
return disabled_condition
|
||||
|
||||
# Check for partials to properly determine if coroutine function
|
||||
check_factory = factory
|
||||
while isinstance(check_factory, ft.partial):
|
||||
|
@ -44,6 +44,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ELSE,
|
||||
CONF_ENABLED,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ENTITY_NAMESPACE,
|
||||
CONF_ERROR,
|
||||
@ -1060,6 +1061,7 @@ SCRIPT_SCHEMA = vol.All(ensure_list, [script_action])
|
||||
SCRIPT_ACTION_BASE_SCHEMA = {
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Optional(CONF_CONTINUE_ON_ERROR): boolean,
|
||||
vol.Optional(CONF_ENABLED): boolean,
|
||||
}
|
||||
|
||||
EVENT_SCHEMA = vol.Schema(
|
||||
@ -1098,7 +1100,10 @@ NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any(
|
||||
vol.Coerce(float), vol.All(str, entity_domain(["input_number", "number", "sensor"]))
|
||||
)
|
||||
|
||||
CONDITION_BASE_SCHEMA = {vol.Optional(CONF_ALIAS): string}
|
||||
CONDITION_BASE_SCHEMA = {
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Optional(CONF_ENABLED): boolean,
|
||||
}
|
||||
|
||||
NUMERIC_STATE_CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
@ -1337,6 +1342,7 @@ TRIGGER_BASE_SCHEMA = vol.Schema(
|
||||
vol.Required(CONF_PLATFORM): str,
|
||||
vol.Optional(CONF_ID): str,
|
||||
vol.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA,
|
||||
vol.Optional(CONF_ENABLED): boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -36,6 +36,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ELSE,
|
||||
CONF_ENABLED,
|
||||
CONF_ERROR,
|
||||
CONF_EVENT,
|
||||
CONF_EVENT_DATA,
|
||||
@ -411,8 +412,17 @@ class _ScriptRun:
|
||||
async with trace_action(self._hass, self, self._stop, self._variables):
|
||||
if self._stop.is_set():
|
||||
return
|
||||
|
||||
action = cv.determine_script_action(self._action)
|
||||
|
||||
if not self._action.get(CONF_ENABLED, True):
|
||||
self._log(
|
||||
"Skipped disabled step %s", self._action.get(CONF_ALIAS, action)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
handler = f"_async_{cv.determine_script_action(self._action)}_step"
|
||||
handler = f"_async_{action}_step"
|
||||
await getattr(self, handler)()
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
self._handle_exception(
|
||||
|
@ -9,7 +9,7 @@ from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_ID, CONF_PLATFORM, CONF_VARIABLES
|
||||
from homeassistant.const import CONF_ENABLED, CONF_ID, CONF_PLATFORM, CONF_VARIABLES
|
||||
from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integration
|
||||
@ -89,6 +89,10 @@ async def async_initialize_triggers(
|
||||
|
||||
triggers = []
|
||||
for idx, conf in enumerate(trigger_config):
|
||||
# Skip triggers that are not enabled
|
||||
if not conf.get(CONF_ENABLED, True):
|
||||
continue
|
||||
|
||||
platform = await _async_get_trigger_platform(hass, conf)
|
||||
trigger_id = conf.get(CONF_ID, f"{idx}")
|
||||
trigger_idx = f"{idx}"
|
||||
|
@ -3004,3 +3004,23 @@ async def test_platform_async_validate_condition_config(hass):
|
||||
platform.async_validate_condition_config.return_value = config
|
||||
await condition.async_validate_condition_config(hass, config)
|
||||
platform.async_validate_condition_config.assert_awaited()
|
||||
|
||||
|
||||
async def test_disabled_condition(hass: HomeAssistant) -> None:
|
||||
"""Test a disabled condition always passes."""
|
||||
config = {
|
||||
"enabled": False,
|
||||
"condition": "state",
|
||||
"entity_id": "binary_sensor.test",
|
||||
"state": "on",
|
||||
}
|
||||
config = cv.CONDITION_SCHEMA(config)
|
||||
config = await condition.async_validate_condition_config(hass, config)
|
||||
test = await condition.async_from_config(hass, config)
|
||||
|
||||
hass.states.async_set("binary_sensor.test", "on")
|
||||
assert test(hass)
|
||||
|
||||
# Still passses, condition is not enabled
|
||||
hass.states.async_set("binary_sensor.test", "off")
|
||||
assert test(hass)
|
||||
|
@ -4436,3 +4436,46 @@ async def test_continue_on_error_unknown_error(hass: HomeAssistant) -> None:
|
||||
},
|
||||
expected_script_execution="error",
|
||||
)
|
||||
|
||||
|
||||
async def test_disabled_actions(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test disabled action steps."""
|
||||
events = async_capture_events(hass, "test_event")
|
||||
|
||||
@callback
|
||||
def broken_service(service: ServiceCall) -> None:
|
||||
"""Break this service with an error."""
|
||||
raise HomeAssistantError("This service should not be called")
|
||||
|
||||
hass.services.async_register("broken", "service", broken_service)
|
||||
|
||||
sequence = cv.SCRIPT_SCHEMA(
|
||||
[
|
||||
{"event": "test_event"},
|
||||
{
|
||||
"alias": "Hello",
|
||||
"enabled": False,
|
||||
"service": "broken.service",
|
||||
},
|
||||
{"alias": "World", "enabled": False, "event": "test_event"},
|
||||
{"event": "test_event"},
|
||||
]
|
||||
)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
|
||||
await script_obj.async_run(context=Context())
|
||||
|
||||
assert len(events) == 2
|
||||
assert "Test Name: Skipped disabled step Hello" in caplog.text
|
||||
assert "Test Name: Skipped disabled step World" in caplog.text
|
||||
|
||||
assert_action_trace(
|
||||
{
|
||||
"0": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
"1": [{}],
|
||||
"2": [{}],
|
||||
"3": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
},
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ from unittest.mock import MagicMock, call, patch
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers.trigger import (
|
||||
_async_get_trigger_platform,
|
||||
async_validate_trigger_config,
|
||||
@ -66,3 +67,39 @@ async def test_if_fires_on_event(hass, calls):
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data["hello"] == "Paulus + test_event"
|
||||
|
||||
|
||||
async def test_if_disabled_trigger_not_firing(
|
||||
hass: HomeAssistant, calls: list[ServiceCall]
|
||||
) -> None:
|
||||
"""Test disabled triggers don't fire."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"automation",
|
||||
{
|
||||
"automation": {
|
||||
"trigger": [
|
||||
{
|
||||
"platform": "event",
|
||||
"event_type": "enabled_trigger_event",
|
||||
},
|
||||
{
|
||||
"enabled": False,
|
||||
"platform": "event",
|
||||
"event_type": "disabled_trigger_event",
|
||||
},
|
||||
],
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
hass.bus.async_fire("disabled_trigger_event")
|
||||
await hass.async_block_till_done()
|
||||
assert not calls
|
||||
|
||||
hass.bus.async_fire("enabled_trigger_event")
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user