From ca9dd0c8332c5a48d44e1f128f63e43d8d1751a6 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Mon, 17 Aug 2020 11:54:56 -0500 Subject: [PATCH] Reorganize trigger code (#38655) --- .../alarm_control_panel/device_trigger.py | 13 +-- .../components/automation/__init__.py | 66 +++---------- homeassistant/components/automation/config.py | 22 ++--- .../binary_sensor/device_trigger.py | 14 +-- .../components/climate/device_trigger.py | 38 ++++---- .../components/cover/device_trigger.py | 33 +++---- .../components/deconz/device_trigger.py | 12 +-- .../device_automation/toggle_entity.py | 18 ++-- .../trigger.py} | 0 .../components/fan/device_trigger.py | 13 +-- .../trigger.py} | 0 .../components/homeassistant/trigger.py | 23 +++++ .../homeassistant/triggers/__init__.py | 1 + .../triggers}/event.py | 0 .../triggers}/homeassistant.py | 0 .../triggers}/numeric_state.py | 0 .../triggers}/state.py | 0 .../triggers}/time.py | 0 .../triggers}/time_pattern.py | 0 .../components/hue/device_trigger.py | 12 +-- .../components/humidifier/device_trigger.py | 18 ++-- .../litejet.py => litejet/trigger.py} | 0 .../components/lock/device_trigger.py | 13 +-- .../components/mqtt/device_trigger.py | 10 +- .../{automation/mqtt.py => mqtt/trigger.py} | 0 .../components/sensor/device_trigger.py | 16 ++-- .../{automation/sun.py => sun/trigger.py} | 0 .../template.py => template/trigger.py} | 2 +- .../components/vacuum/device_trigger.py | 13 +-- .../webhook.py => webhook/trigger.py} | 4 +- .../components/zha/device_trigger.py | 12 +-- .../{automation/zone.py => zone/trigger.py} | 0 homeassistant/helpers/config_validation.py | 4 + homeassistant/helpers/trigger.py | 92 +++++++++++++++++++ tests/components/automation/test_init.py | 16 ++++ .../test_trigger.py} | 0 .../homeassistant/triggers/__init__.py | 1 + .../triggers}/test_event.py | 0 .../triggers}/test_homeassistant.py | 0 .../triggers}/test_numeric_state.py | 10 +- .../triggers}/test_state.py | 5 +- .../triggers}/test_time.py | 2 +- .../triggers}/test_time_pattern.py | 0 .../test_trigger.py} | 0 tests/components/mqtt/test_discovery.py | 2 + .../test_mqtt.py => mqtt/test_trigger.py} | 0 .../test_sun.py => sun/test_trigger.py} | 0 .../test_trigger.py} | 3 +- .../test_trigger.py} | 0 .../test_zone.py => zone/test_trigger.py} | 0 tests/helpers/test_trigger.py | 12 +++ 51 files changed, 306 insertions(+), 194 deletions(-) rename homeassistant/components/{automation/device.py => device_automation/trigger.py} (100%) rename homeassistant/components/{automation/geo_location.py => geo_location/trigger.py} (100%) create mode 100644 homeassistant/components/homeassistant/trigger.py create mode 100644 homeassistant/components/homeassistant/triggers/__init__.py rename homeassistant/components/{automation => homeassistant/triggers}/event.py (100%) rename homeassistant/components/{automation => homeassistant/triggers}/homeassistant.py (100%) rename homeassistant/components/{automation => homeassistant/triggers}/numeric_state.py (100%) rename homeassistant/components/{automation => homeassistant/triggers}/state.py (100%) rename homeassistant/components/{automation => homeassistant/triggers}/time.py (100%) rename homeassistant/components/{automation => homeassistant/triggers}/time_pattern.py (100%) rename homeassistant/components/{automation/litejet.py => litejet/trigger.py} (100%) rename homeassistant/components/{automation/mqtt.py => mqtt/trigger.py} (100%) rename homeassistant/components/{automation/sun.py => sun/trigger.py} (100%) rename homeassistant/components/{automation/template.py => template/trigger.py} (97%) rename homeassistant/components/{automation/webhook.py => webhook/trigger.py} (95%) rename homeassistant/components/{automation/zone.py => zone/trigger.py} (100%) create mode 100644 homeassistant/helpers/trigger.py rename tests/components/{automation/test_geo_location.py => geo_location/test_trigger.py} (100%) create mode 100644 tests/components/homeassistant/triggers/__init__.py rename tests/components/{automation => homeassistant/triggers}/test_event.py (100%) rename tests/components/{automation => homeassistant/triggers}/test_homeassistant.py (100%) rename tests/components/{automation => homeassistant/triggers}/test_numeric_state.py (99%) rename tests/components/{automation => homeassistant/triggers}/test_state.py (99%) rename tests/components/{automation => homeassistant/triggers}/test_time.py (99%) rename tests/components/{automation => homeassistant/triggers}/test_time_pattern.py (100%) rename tests/components/{automation/test_litejet.py => litejet/test_trigger.py} (100%) rename tests/components/{automation/test_mqtt.py => mqtt/test_trigger.py} (100%) rename tests/components/{automation/test_sun.py => sun/test_trigger.py} (100%) rename tests/components/{automation/test_template.py => template/test_trigger.py} (99%) rename tests/components/{automation/test_webhook.py => webhook/test_trigger.py} (100%) rename tests/components/{automation/test_zone.py => zone/test_trigger.py} (100%) create mode 100644 tests/helpers/test_trigger.py diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 849da062665..eeea1dbbf33 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -8,8 +8,9 @@ from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, ) -from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -151,13 +152,13 @@ async def async_attach_trigger( to_state = STATE_ALARM_ARMED_NIGHT state_config = { - state.CONF_PLATFORM: "state", + state_trigger.CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_TO: to_state, + state_trigger.CONF_TO: to_state, } if from_state: - state_config[state.CONF_FROM] = from_state - state_config = state.TRIGGER_SCHEMA(state_config) - return await state.async_attach_trigger( + state_config[state_trigger.CONF_FROM] = from_state + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 5da1aedab25..b4caf9bcd49 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,8 +1,6 @@ """Allow to set up simple automation rules via the config file.""" -import asyncio -import importlib import logging -from typing import Any, Awaitable, Callable, List, Optional, Set +from typing import Any, Awaitable, Callable, List, Optional, Set, cast import voluptuous as vol @@ -46,6 +44,7 @@ from homeassistant.helpers.script import ( make_script_schema, ) from homeassistant.helpers.service import async_register_admin_service +from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime, utcnow @@ -90,26 +89,6 @@ _LOGGER = logging.getLogger(__name__) AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] -def _platform_validator(config): - """Validate it is a valid platform.""" - try: - platform = importlib.import_module(f".{config[CONF_PLATFORM]}", __name__) - except ImportError: - raise vol.Invalid("Invalid platform specified") from None - - return platform.TRIGGER_SCHEMA(config) - - -_TRIGGER_SCHEMA = vol.All( - cv.ensure_list, - [ - vol.All( - vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA), - _platform_validator, - ) - ], -) - _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) PLATFORM_SCHEMA = vol.All( @@ -122,7 +101,7 @@ PLATFORM_SCHEMA = vol.All( vol.Optional(CONF_DESCRIPTION): cv.string, vol.Optional(CONF_INITIAL_STATE): cv.boolean, vol.Optional(CONF_HIDE_ENTITY): cv.boolean, - vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, + vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA, vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, }, @@ -485,36 +464,19 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self, home_assistant_start: bool ) -> Optional[Callable[[], None]]: """Set up the triggers.""" - info = {"name": self._name, "home_assistant_start": home_assistant_start} - triggers = [] - for conf in self._trigger_config: - platform = importlib.import_module(f".{conf[CONF_PLATFORM]}", __name__) + def log_cb(level, msg): + self._logger.log(level, "%s %s", msg, self._name) - triggers.append( - platform.async_attach_trigger( # type: ignore - self.hass, conf, self.async_trigger, info - ) - ) - - results = await asyncio.gather(*triggers) - - if None in results: - self._logger.error("Error setting up trigger %s", self._name) - - removes = [remove for remove in results if remove is not None] - if not removes: - return None - - self._logger.info("Initialized trigger %s", self._name) - - @callback - def remove_triggers(): - """Remove attached triggers.""" - for remove in removes: - remove() - - return remove_triggers + return await async_initialize_triggers( + cast(HomeAssistant, self.hass), + self._trigger_config, + self.async_trigger, + DOMAIN, + self._name, + log_cb, + home_assistant_start, + ) @property def device_state_attributes(self): diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 8d956258134..2ac2b8d9354 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -1,6 +1,5 @@ """Config validation helper for the automation integration.""" import asyncio -import importlib import voluptuous as vol @@ -8,10 +7,11 @@ from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) from homeassistant.config import async_log_exception, config_without_domain -from homeassistant.const import CONF_PLATFORM from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import condition, config_per_platform +from homeassistant.helpers import config_per_platform +from homeassistant.helpers.condition import async_validate_condition_config from homeassistant.helpers.script import async_validate_action_config +from homeassistant.helpers.trigger import async_validate_trigger_config from homeassistant.loader import IntegrationNotFound from . import CONF_ACTION, CONF_CONDITION, CONF_TRIGGER, DOMAIN, PLATFORM_SCHEMA @@ -24,22 +24,14 @@ async def async_validate_config_item(hass, config, full_config=None): """Validate config item.""" config = PLATFORM_SCHEMA(config) - triggers = [] - for trigger in config[CONF_TRIGGER]: - trigger_platform = importlib.import_module( - f"..{trigger[CONF_PLATFORM]}", __name__ - ) - if hasattr(trigger_platform, "async_validate_trigger_config"): - trigger = await trigger_platform.async_validate_trigger_config( - hass, trigger - ) - triggers.append(trigger) - config[CONF_TRIGGER] = triggers + config[CONF_TRIGGER] = await async_validate_trigger_config( + hass, config[CONF_TRIGGER] + ) if CONF_CONDITION in config: config[CONF_CONDITION] = await asyncio.gather( *[ - condition.async_validate_condition_config(hass, cond) + async_validate_condition_config(hass, cond) for cond in config[CONF_CONDITION] ] ) diff --git a/homeassistant/components/binary_sensor/device_trigger.py b/homeassistant/components/binary_sensor/device_trigger.py index d50cc20c1ae..f7f0c53a698 100644 --- a/homeassistant/components/binary_sensor/device_trigger.py +++ b/homeassistant/components/binary_sensor/device_trigger.py @@ -1,12 +1,12 @@ """Provides device triggers for binary sensors.""" import voluptuous as vol -from homeassistant.components.automation import state as state_automation from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.const import ( CONF_TURNED_OFF, CONF_TURNED_ON, ) +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_registry import async_entries_for_device @@ -197,16 +197,16 @@ async def async_attach_trigger(hass, config, action, automation_info): to_state = "off" state_config = { - state_automation.CONF_PLATFORM: "state", - state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_automation.CONF_FROM: from_state, - state_automation.CONF_TO: to_state, + state_trigger.CONF_PLATFORM: "state", + state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + state_trigger.CONF_FROM: from_state, + state_trigger.CONF_TO: to_state, } if CONF_FOR in config: state_config[CONF_FOR] = config[CONF_FOR] - state_config = state_automation.TRIGGER_SCHEMA(state_config) - return await state_automation.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 187f50bc984..1dabe65e6d0 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -3,12 +3,12 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import ( - AutomationActionType, - numeric_state as numeric_state_automation, - state as state_automation, -) +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import ( + numeric_state as numeric_state_trigger, + state as state_trigger, +) from homeassistant.const import ( CONF_ABOVE, CONF_BELOW, @@ -36,7 +36,7 @@ HVAC_MODE_TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( { vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_TYPE): "hvac_mode_changed", - vol.Required(state_automation.CONF_TO): vol.In(const.HVAC_MODES), + vol.Required(state_trigger.CONF_TO): vol.In(const.HVAC_MODES), } ) @@ -118,34 +118,34 @@ async def async_attach_trigger( if trigger_type == "hvac_mode_changed": state_config = { - state_automation.CONF_PLATFORM: "state", - state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_automation.CONF_TO: config[state_automation.CONF_TO], - state_automation.CONF_FROM: [ + state_trigger.CONF_PLATFORM: "state", + state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + state_trigger.CONF_TO: config[state_trigger.CONF_TO], + state_trigger.CONF_FROM: [ mode for mode in const.HVAC_MODES - if mode != config[state_automation.CONF_TO] + if mode != config[state_trigger.CONF_TO] ], } if CONF_FOR in config: state_config[CONF_FOR] = config[CONF_FOR] - state_config = state_automation.TRIGGER_SCHEMA(state_config) - return await state_automation.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) numeric_state_config = { - numeric_state_automation.CONF_PLATFORM: "numeric_state", - numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], } if trigger_type == "current_temperature_changed": numeric_state_config[ - numeric_state_automation.CONF_VALUE_TEMPLATE + numeric_state_trigger.CONF_VALUE_TEMPLATE ] = "{{ state.attributes.current_temperature }}" else: numeric_state_config[ - numeric_state_automation.CONF_VALUE_TEMPLATE + numeric_state_trigger.CONF_VALUE_TEMPLATE ] = "{{ state.attributes.current_humidity }}" if CONF_ABOVE in config: @@ -155,8 +155,8 @@ async def async_attach_trigger( if CONF_FOR in config: numeric_state_config[CONF_FOR] = config[CONF_FOR] - numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config) - return await numeric_state_automation.async_attach_trigger( + numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config) + return await numeric_state_trigger.async_attach_trigger( hass, numeric_state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 988427003e7..764cc173e5f 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -3,12 +3,12 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import ( - AutomationActionType, - numeric_state as numeric_state_automation, - state as state_automation, -) +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import ( + numeric_state as numeric_state_trigger, + state as state_trigger, +) from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_ABOVE, @@ -18,6 +18,7 @@ from homeassistant.const import ( CONF_ENTITY_ID, CONF_PLATFORM, CONF_TYPE, + CONF_VALUE_TEMPLATE, STATE_CLOSED, STATE_CLOSING, STATE_OPEN, @@ -182,12 +183,12 @@ async def async_attach_trigger( to_state = STATE_CLOSING state_config = { - state_automation.CONF_PLATFORM: "state", + CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_automation.CONF_TO: to_state, + state_trigger.CONF_TO: to_state, } - state_config = state_automation.TRIGGER_SCHEMA(state_config) - return await state_automation.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) @@ -200,13 +201,13 @@ async def async_attach_trigger( value_template = f"{{{{ state.attributes.{position} }}}}" numeric_state_config = { - numeric_state_automation.CONF_PLATFORM: "numeric_state", - numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - numeric_state_automation.CONF_BELOW: max_pos, - numeric_state_automation.CONF_ABOVE: min_pos, - numeric_state_automation.CONF_VALUE_TEMPLATE: value_template, + CONF_PLATFORM: "numeric_state", + CONF_ENTITY_ID: config[CONF_ENTITY_ID], + CONF_BELOW: max_pos, + CONF_ABOVE: min_pos, + CONF_VALUE_TEMPLATE: value_template, } - numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config) - return await numeric_state_automation.async_attach_trigger( + numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config) + return await numeric_state_trigger.async_attach_trigger( hass, numeric_state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index f4019e98fbd..bddde34a7de 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -1,11 +1,11 @@ """Provides device automations for deconz events.""" import voluptuous as vol -import homeassistant.components.automation.event as event from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) +from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -432,13 +432,13 @@ async def async_attach_trigger(hass, config, action, automation_info): event_id = deconz_event.serial event_config = { - event.CONF_PLATFORM: "event", - event.CONF_EVENT_TYPE: CONF_DECONZ_EVENT, - event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, **trigger}, + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: CONF_DECONZ_EVENT, + event_trigger.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, **trigger}, } - event_config = event.TRIGGER_SCHEMA(event_config) - return await event.async_attach_trigger( + event_config = event_trigger.TRIGGER_SCHEMA(event_config) + return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index e9a65f7bedd..61c50da6868 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -3,10 +3,7 @@ from typing import Any, Dict, List import voluptuous as vol -from homeassistant.components.automation import ( - AutomationActionType, - state as state_automation, -) +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation.const import ( CONF_IS_OFF, CONF_IS_ON, @@ -16,6 +13,7 @@ from homeassistant.components.device_automation.const import ( CONF_TURNED_OFF, CONF_TURNED_ON, ) +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, @@ -157,16 +155,16 @@ async def async_attach_trigger( from_state = "on" to_state = "off" state_config = { - state_automation.CONF_PLATFORM: "state", - state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_automation.CONF_FROM: from_state, - state_automation.CONF_TO: to_state, + CONF_PLATFORM: "state", + state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + state_trigger.CONF_FROM: from_state, + state_trigger.CONF_TO: to_state, } if CONF_FOR in config: state_config[CONF_FOR] = config[CONF_FOR] - state_config = state_automation.TRIGGER_SCHEMA(state_config) - return await state_automation.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/device.py b/homeassistant/components/device_automation/trigger.py similarity index 100% rename from homeassistant/components/automation/device.py rename to homeassistant/components/device_automation/trigger.py diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index 3bfeb5ee36b..c78ebcfffe4 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -3,8 +3,9 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -80,12 +81,12 @@ async def async_attach_trigger( to_state = STATE_OFF state_config = { - state.CONF_PLATFORM: "state", + state_trigger.CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_FROM: from_state, - state.CONF_TO: to_state, + state_trigger.CONF_FROM: from_state, + state_trigger.CONF_TO: to_state, } - state_config = state.TRIGGER_SCHEMA(state_config) - return await state.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/geo_location.py b/homeassistant/components/geo_location/trigger.py similarity index 100% rename from homeassistant/components/automation/geo_location.py rename to homeassistant/components/geo_location/trigger.py diff --git a/homeassistant/components/homeassistant/trigger.py b/homeassistant/components/homeassistant/trigger.py new file mode 100644 index 00000000000..ca77747cd96 --- /dev/null +++ b/homeassistant/components/homeassistant/trigger.py @@ -0,0 +1,23 @@ +"""Home Assistant trigger dispatcher.""" +import importlib + +from homeassistant.const import CONF_PLATFORM + + +def _get_trigger_platform(config): + return importlib.import_module(f"..triggers.{config[CONF_PLATFORM]}", __name__) + + +async def async_validate_trigger_config(hass, config): + """Validate config.""" + platform = _get_trigger_platform(config) + if hasattr(platform, "async_validate_trigger_config"): + return await getattr(platform, "async_validate_trigger_config")(hass, config) + + return platform.TRIGGER_SCHEMA(config) + + +async def async_attach_trigger(hass, config, action, automation_info): + """Attach trigger of specified platform.""" + platform = _get_trigger_platform(config) + return await platform.async_attach_trigger(hass, config, action, automation_info) diff --git a/homeassistant/components/homeassistant/triggers/__init__.py b/homeassistant/components/homeassistant/triggers/__init__.py new file mode 100644 index 00000000000..2a995eaa3f1 --- /dev/null +++ b/homeassistant/components/homeassistant/triggers/__init__.py @@ -0,0 +1 @@ +"""Home Assistant triggers.""" diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/homeassistant/triggers/event.py similarity index 100% rename from homeassistant/components/automation/event.py rename to homeassistant/components/homeassistant/triggers/event.py diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py similarity index 100% rename from homeassistant/components/automation/homeassistant.py rename to homeassistant/components/homeassistant/triggers/homeassistant.py diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py similarity index 100% rename from homeassistant/components/automation/numeric_state.py rename to homeassistant/components/homeassistant/triggers/numeric_state.py diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/homeassistant/triggers/state.py similarity index 100% rename from homeassistant/components/automation/state.py rename to homeassistant/components/homeassistant/triggers/state.py diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/homeassistant/triggers/time.py similarity index 100% rename from homeassistant/components/automation/time.py rename to homeassistant/components/homeassistant/triggers/time.py diff --git a/homeassistant/components/automation/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py similarity index 100% rename from homeassistant/components/automation/time_pattern.py rename to homeassistant/components/homeassistant/triggers/time_pattern.py diff --git a/homeassistant/components/hue/device_trigger.py b/homeassistant/components/hue/device_trigger.py index 8a4b2eab714..9e56a253a58 100644 --- a/homeassistant/components/hue/device_trigger.py +++ b/homeassistant/components/hue/device_trigger.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -import homeassistant.components.automation.event as event from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) +from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -139,13 +139,13 @@ async def async_attach_trigger(hass, config, action, automation_info): trigger = REMOTES[device.model][trigger] event_config = { - event.CONF_PLATFORM: "event", - event.CONF_EVENT_TYPE: CONF_HUE_EVENT, - event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: hue_event.unique_id, **trigger}, + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: CONF_HUE_EVENT, + event_trigger.CONF_EVENT_DATA: {CONF_UNIQUE_ID: hue_event.unique_id, **trigger}, } - event_config = event.TRIGGER_SCHEMA(event_config) - return await event.async_attach_trigger( + event_config = event_trigger.TRIGGER_SCHEMA(event_config) + return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index 906fb96bede..6829c87708d 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -3,14 +3,14 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import ( - AutomationActionType, - numeric_state as numeric_state_automation, -) +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import ( TRIGGER_BASE_SCHEMA, toggle_entity, ) +from homeassistant.components.homeassistant.triggers import ( + numeric_state as numeric_state_trigger, +) from homeassistant.const import ( CONF_ABOVE, CONF_BELOW, @@ -81,9 +81,9 @@ async def async_attach_trigger( if trigger_type == "target_humidity_changed": numeric_state_config = { - numeric_state_automation.CONF_PLATFORM: "numeric_state", - numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - numeric_state_automation.CONF_VALUE_TEMPLATE: "{{ state.attributes.humidity }}", + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + numeric_state_trigger.CONF_VALUE_TEMPLATE: "{{ state.attributes.humidity }}", } if CONF_ABOVE in config: @@ -93,10 +93,10 @@ async def async_attach_trigger( if CONF_FOR in config: numeric_state_config[CONF_FOR] = config[CONF_FOR] - numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA( + numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA( numeric_state_config ) - return await numeric_state_automation.async_attach_trigger( + return await numeric_state_trigger.async_attach_trigger( hass, numeric_state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/litejet/trigger.py similarity index 100% rename from homeassistant/components/automation/litejet.py rename to homeassistant/components/litejet/trigger.py diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 9db2822a591..091811446b5 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -3,8 +3,9 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -80,12 +81,12 @@ async def async_attach_trigger( to_state = STATE_UNLOCKED state_config = { - state.CONF_PLATFORM: "state", + CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_FROM: from_state, - state.CONF_TO: to_state, + state_trigger.CONF_FROM: from_state, + state_trigger.CONF_TO: to_state, } - state_config = state.TRIGGER_SCHEMA(state_config) - return await state.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index cf8d0f250d1..01eb84e66e7 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -7,7 +7,6 @@ import voluptuous as vol from homeassistant.components import mqtt from homeassistant.components.automation import AutomationActionType -import homeassistant.components.automation.mqtt as automation_mqtt from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -27,6 +26,7 @@ from . import ( DOMAIN, cleanup_device_registry, debug_info, + trigger as mqtt_trigger, ) from .discovery import MQTT_DISCOVERY_UPDATED, clear_discovery_hash @@ -83,16 +83,16 @@ class TriggerInstance: async def async_attach_trigger(self): """Attach MQTT trigger.""" mqtt_config = { - automation_mqtt.CONF_TOPIC: self.trigger.topic, - automation_mqtt.CONF_ENCODING: DEFAULT_ENCODING, - automation_mqtt.CONF_QOS: self.trigger.qos, + mqtt_trigger.CONF_TOPIC: self.trigger.topic, + mqtt_trigger.CONF_ENCODING: DEFAULT_ENCODING, + mqtt_trigger.CONF_QOS: self.trigger.qos, } if self.trigger.payload: mqtt_config[CONF_PAYLOAD] = self.trigger.payload if self.remove: self.remove() - self.remove = await automation_mqtt.async_attach_trigger( + self.remove = await mqtt_trigger.async_attach_trigger( self.trigger.hass, mqtt_config, self.action, self.automation_info, ) diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/mqtt/trigger.py similarity index 100% rename from homeassistant/components/automation/mqtt.py rename to homeassistant/components/mqtt/trigger.py diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 48c0ec493c9..77f6afd9acf 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -1,11 +1,13 @@ """Provides device triggers for sensors.""" import voluptuous as vol -import homeassistant.components.automation.numeric_state as numeric_state_automation from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) +from homeassistant.components.homeassistant.triggers import ( + numeric_state as numeric_state_trigger, +) from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, @@ -100,18 +102,18 @@ TRIGGER_SCHEMA = vol.All( async def async_attach_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" numeric_state_config = { - numeric_state_automation.CONF_PLATFORM: "numeric_state", - numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID], + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], } if CONF_ABOVE in config: - numeric_state_config[numeric_state_automation.CONF_ABOVE] = config[CONF_ABOVE] + numeric_state_config[numeric_state_trigger.CONF_ABOVE] = config[CONF_ABOVE] if CONF_BELOW in config: - numeric_state_config[numeric_state_automation.CONF_BELOW] = config[CONF_BELOW] + numeric_state_config[numeric_state_trigger.CONF_BELOW] = config[CONF_BELOW] if CONF_FOR in config: numeric_state_config[CONF_FOR] = config[CONF_FOR] - numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config) - return await numeric_state_automation.async_attach_trigger( + numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config) + return await numeric_state_trigger.async_attach_trigger( hass, numeric_state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/sun/trigger.py similarity index 100% rename from homeassistant/components/automation/sun.py rename to homeassistant/components/sun/trigger.py diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/template/trigger.py similarity index 97% rename from homeassistant/components/automation/template.py rename to homeassistant/components/template/trigger.py index f376cedd0b0..7cbc1a8ffd4 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/template/trigger.py @@ -23,7 +23,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema( async def async_attach_trigger( - hass, config, action, automation_info, *, platform_type="numeric_state" + hass, config, action, automation_info, *, platform_type="template" ): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index ee225ab3caa..29fc5628b22 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -3,8 +3,9 @@ from typing import List import voluptuous as vol -from homeassistant.components.automation import AutomationActionType, state +from homeassistant.components.automation import AutomationActionType from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( CONF_DEVICE_ID, CONF_DOMAIN, @@ -77,12 +78,12 @@ async def async_attach_trigger( to_state = STATE_DOCKED state_config = { - state.CONF_PLATFORM: "state", + CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_FROM: from_state, - state.CONF_TO: to_state, + state_trigger.CONF_FROM: from_state, + state_trigger.CONF_TO: to_state, } - state_config = state.TRIGGER_SCHEMA(state_config) - return await state.async_attach_trigger( + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/webhook/trigger.py similarity index 95% rename from homeassistant/components/automation/webhook.py rename to homeassistant/components/webhook/trigger.py index 5d01c6454a8..38ae9c5e364 100644 --- a/homeassistant/components/automation/webhook.py +++ b/homeassistant/components/webhook/trigger.py @@ -9,8 +9,6 @@ from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from . import DOMAIN as AUTOMATION_DOMAIN - # mypy: allow-untyped-defs DEPENDENCIES = ("webhook",) @@ -39,7 +37,7 @@ async def async_attach_trigger(hass, config, action, automation_info): """Trigger based on incoming webhooks.""" webhook_id = config.get(CONF_WEBHOOK_ID) hass.components.webhook.async_register( - AUTOMATION_DOMAIN, + automation_info["domain"], automation_info["name"], webhook_id, partial(_handle_webhook, action), diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index 5f842d7f380..e92d0fb3028 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -1,11 +1,11 @@ """Provides device automations for ZHA devices that emit events.""" import voluptuous as vol -import homeassistant.components.automation.event as event from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) +from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from . import DOMAIN @@ -54,13 +54,13 @@ async def async_attach_trigger(hass, config, action, automation_info): trigger = zha_device.device_automation_triggers[trigger] event_config = { - event.CONF_PLATFORM: "event", - event.CONF_EVENT_TYPE: ZHA_EVENT, - event.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger}, + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: ZHA_EVENT, + event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger}, } - event_config = event.TRIGGER_SCHEMA(event_config) - return await event.async_attach_trigger( + event_config = event_trigger.TRIGGER_SCHEMA(event_config) + return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/zone/trigger.py similarity index 100% rename from homeassistant/components/automation/zone.py rename to homeassistant/components/zone/trigger.py diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 5d883dc8a05..3b8988b325b 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -995,6 +995,10 @@ CONDITION_SCHEMA: vol.Schema = key_value_schemas( }, ) +TRIGGER_SCHEMA = vol.All( + ensure_list, [vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA)] +) + _SCRIPT_DELAY_SCHEMA = vol.Schema( { vol.Optional(CONF_ALIAS): string, diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py new file mode 100644 index 00000000000..9918ad37fe9 --- /dev/null +++ b/homeassistant/helpers/trigger.py @@ -0,0 +1,92 @@ +"""Triggers.""" +import asyncio +import logging +from typing import Any, Callable, List, Optional + +import voluptuous as vol + +from homeassistant.const import CONF_PLATFORM +from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.loader import IntegrationNotFound, async_get_integration + +_PLATFORM_ALIASES = { + "device_automation": ("device",), + "homeassistant": ("event", "numeric_state", "state", "time_pattern", "time"), +} + + +async def _async_get_trigger_platform( + hass: HomeAssistantType, config: ConfigType +) -> Any: + platform = config[CONF_PLATFORM] + for alias, triggers in _PLATFORM_ALIASES.items(): + if platform in triggers: + platform = alias + break + try: + integration = await async_get_integration(hass, platform) + except IntegrationNotFound: + raise vol.Invalid(f"Invalid platform '{platform}' specified") from None + try: + return integration.get_platform("trigger") + except ImportError: + raise vol.Invalid( + f"Integration '{platform}' does not provide trigger support" + ) from None + + +async def async_validate_trigger_config( + hass: HomeAssistantType, trigger_config: List[ConfigType] +) -> List[ConfigType]: + """Validate triggers.""" + config = [] + for conf in trigger_config: + platform = await _async_get_trigger_platform(hass, conf) + if hasattr(platform, "async_validate_trigger_config"): + conf = await platform.async_validate_trigger_config(hass, conf) + else: + conf = platform.TRIGGER_SCHEMA(conf) + config.append(conf) + return config + + +async def async_initialize_triggers( + hass: HomeAssistantType, + trigger_config: List[ConfigType], + action: Callable, + domain: str, + name: str, + log_cb: Callable, + home_assistant_start: bool = False, +) -> Optional[CALLBACK_TYPE]: + """Initialize triggers.""" + info = { + "domain": domain, + "name": name, + "home_assistant_start": home_assistant_start, + } + + triggers = [] + for conf in trigger_config: + platform = await _async_get_trigger_platform(hass, conf) + triggers.append(platform.async_attach_trigger(hass, conf, action, info)) + + removes = await asyncio.gather(*triggers) + + if None in removes: + log_cb(logging.ERROR, "Error setting up trigger") + + removes = list(filter(None, removes)) + if not removes: + return None + + log_cb(logging.INFO, "Initialized trigger") + + @callback + def remove_triggers(): # type: ignore + """Remove triggers.""" + for remove in removes: + remove() + + return remove_triggers diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index a832b26d752..483f8003e3f 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -861,6 +861,22 @@ async def test_automation_not_trigger_on_bootstrap(hass): assert ["hello.world"] == calls[0].data.get(ATTR_ENTITY_ID) +async def test_automation_bad_trigger(hass, caplog): + """Test bad trigger configuration.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "alias": "hello", + "trigger": {"platform": "automation"}, + "action": [], + } + }, + ) + assert "Integration 'automation' does not provide trigger support." in caplog.text + + async def test_automation_with_error_in_script(hass, caplog): """Test automation with an error in script.""" assert await async_setup_component( diff --git a/tests/components/automation/test_geo_location.py b/tests/components/geo_location/test_trigger.py similarity index 100% rename from tests/components/automation/test_geo_location.py rename to tests/components/geo_location/test_trigger.py diff --git a/tests/components/homeassistant/triggers/__init__.py b/tests/components/homeassistant/triggers/__init__.py new file mode 100644 index 00000000000..f40f513e89b --- /dev/null +++ b/tests/components/homeassistant/triggers/__init__.py @@ -0,0 +1 @@ +"""Test core triggers.""" diff --git a/tests/components/automation/test_event.py b/tests/components/homeassistant/triggers/test_event.py similarity index 100% rename from tests/components/automation/test_event.py rename to tests/components/homeassistant/triggers/test_event.py diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/homeassistant/triggers/test_homeassistant.py similarity index 100% rename from tests/components/automation/test_homeassistant.py rename to tests/components/homeassistant/triggers/test_homeassistant.py diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py similarity index 99% rename from tests/components/automation/test_numeric_state.py rename to tests/components/homeassistant/triggers/test_numeric_state.py index e4180ad2e6c..01b276c236a 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -5,7 +5,9 @@ import pytest import voluptuous as vol import homeassistant.components.automation as automation -from homeassistant.components.automation import numeric_state +from homeassistant.components.homeassistant.triggers import ( + numeric_state as numeric_state_trigger, +) from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -776,7 +778,7 @@ async def test_if_fails_setup_bad_for(hass, calls): }, ) - with patch.object(automation.numeric_state, "_LOGGER") as mock_logger: + with patch.object(numeric_state_trigger, "_LOGGER") as mock_logger: hass.states.async_set("test.entity", 9) await hass.async_block_till_done() assert mock_logger.error.called @@ -1164,7 +1166,7 @@ async def test_invalid_for_template(hass, calls): }, ) - with patch.object(automation.numeric_state, "_LOGGER") as mock_logger: + with patch.object(numeric_state_trigger, "_LOGGER") as mock_logger: hass.states.async_set("test.entity", 9) await hass.async_block_till_done() assert mock_logger.error.called @@ -1234,6 +1236,6 @@ async def test_if_fires_on_entities_change_overlap_for_template(hass, calls): def test_below_above(): """Test above cannot be above below.""" with pytest.raises(vol.Invalid): - numeric_state.TRIGGER_SCHEMA( + numeric_state_trigger.TRIGGER_SCHEMA( {"platform": "numeric_state", "above": 1200, "below": 1000} ) diff --git a/tests/components/automation/test_state.py b/tests/components/homeassistant/triggers/test_state.py similarity index 99% rename from tests/components/automation/test_state.py rename to tests/components/homeassistant/triggers/test_state.py index 9842818efab..7f256293025 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -4,6 +4,7 @@ from datetime import timedelta import pytest import homeassistant.components.automation as automation +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -327,7 +328,7 @@ async def test_if_fails_setup_bad_for(hass, calls): }, ) - with patch.object(automation.state, "_LOGGER") as mock_logger: + with patch.object(state_trigger, "_LOGGER") as mock_logger: hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert mock_logger.error.called @@ -942,7 +943,7 @@ async def test_invalid_for_template_1(hass, calls): }, ) - with patch.object(automation.state, "_LOGGER") as mock_logger: + with patch.object(state_trigger, "_LOGGER") as mock_logger: hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert mock_logger.error.called diff --git a/tests/components/automation/test_time.py b/tests/components/homeassistant/triggers/test_time.py similarity index 99% rename from tests/components/automation/test_time.py rename to tests/components/homeassistant/triggers/test_time.py index b7540af3673..0eaa233bd3d 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/homeassistant/triggers/test_time.py @@ -361,7 +361,7 @@ async def test_untrack_time_change(hass): """Test for removing tracked time changes.""" mock_track_time_change = Mock() with patch( - "homeassistant.components.automation.time.async_track_time_change", + "homeassistant.components.homeassistant.triggers.time.async_track_time_change", return_value=mock_track_time_change, ): assert await async_setup_component( diff --git a/tests/components/automation/test_time_pattern.py b/tests/components/homeassistant/triggers/test_time_pattern.py similarity index 100% rename from tests/components/automation/test_time_pattern.py rename to tests/components/homeassistant/triggers/test_time_pattern.py diff --git a/tests/components/automation/test_litejet.py b/tests/components/litejet/test_trigger.py similarity index 100% rename from tests/components/automation/test_litejet.py rename to tests/components/litejet/test_trigger.py diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index c1388aeb1c1..59687aaff62 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -419,6 +419,8 @@ async def test_missing_discover_abbreviations(hass, mqtt_mock, caplog): missing = [] regex = re.compile(r"(CONF_[a-zA-Z\d_]*) *= *[\'\"]([a-zA-Z\d_]*)[\'\"]") for fil in Path(mqtt.__file__).parent.rglob("*.py"): + if fil.name == "trigger.py": + continue with open(fil) as file: matches = re.findall(regex, file.read()) for match in matches: diff --git a/tests/components/automation/test_mqtt.py b/tests/components/mqtt/test_trigger.py similarity index 100% rename from tests/components/automation/test_mqtt.py rename to tests/components/mqtt/test_trigger.py diff --git a/tests/components/automation/test_sun.py b/tests/components/sun/test_trigger.py similarity index 100% rename from tests/components/automation/test_sun.py rename to tests/components/sun/test_trigger.py diff --git a/tests/components/automation/test_template.py b/tests/components/template/test_trigger.py similarity index 99% rename from tests/components/automation/test_template.py rename to tests/components/template/test_trigger.py index 22f16c3c824..70455fa4c22 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/template/test_trigger.py @@ -5,6 +5,7 @@ from unittest import mock import pytest import homeassistant.components.automation as automation +from homeassistant.components.template import trigger as template_trigger from homeassistant.core import Context, callback from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -782,7 +783,7 @@ async def test_invalid_for_template_1(hass, calls): }, ) - with mock.patch.object(automation.template, "_LOGGER") as mock_logger: + with mock.patch.object(template_trigger, "_LOGGER") as mock_logger: hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert mock_logger.error.called diff --git a/tests/components/automation/test_webhook.py b/tests/components/webhook/test_trigger.py similarity index 100% rename from tests/components/automation/test_webhook.py rename to tests/components/webhook/test_trigger.py diff --git a/tests/components/automation/test_zone.py b/tests/components/zone/test_trigger.py similarity index 100% rename from tests/components/automation/test_zone.py rename to tests/components/zone/test_trigger.py diff --git a/tests/helpers/test_trigger.py b/tests/helpers/test_trigger.py new file mode 100644 index 00000000000..b4bfb881186 --- /dev/null +++ b/tests/helpers/test_trigger.py @@ -0,0 +1,12 @@ +"""The tests for the trigger helper.""" +import pytest +import voluptuous as vol + +from homeassistant.helpers.trigger import async_validate_trigger_config + + +async def test_bad_trigger_platform(hass): + """Test bad trigger platform.""" + with pytest.raises(vol.Invalid) as ex: + await async_validate_trigger_config(hass, [{"platform": "not_a_platform"}]) + assert "Invalid platform 'not_a_platform' specified" in str(ex)