1 """Config validation helper for the automation integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from contextlib
import suppress
7 from enum
import StrEnum
10 import voluptuous
as vol
11 from voluptuous.humanize
import humanize_error
39 CONF_TRIGGER_VARIABLES,
44 from .helpers
import async_get_blueprints
46 PACKAGE_MERGE_HINT =
"list"
48 _MINIMAL_PLATFORM_SCHEMA = vol.Schema(
51 CONF_ALIAS: cv.string,
52 vol.Optional(CONF_DESCRIPTION): cv.string,
54 extra=vol.ALLOW_EXTRA,
59 """Backward compatibility for automations."""
61 if not isinstance(value, dict):
65 if CONF_TRIGGER
in value:
66 if CONF_TRIGGERS
in value:
68 "Cannot specify both 'trigger' and 'triggers'. Please use 'triggers' only."
70 value[CONF_TRIGGERS] = value.pop(CONF_TRIGGER)
73 if CONF_CONDITION
in value:
74 if CONF_CONDITIONS
in value:
76 "Cannot specify both 'condition' and 'conditions'. Please use 'conditions' only."
78 value[CONF_CONDITIONS] = value.pop(CONF_CONDITION)
81 if CONF_ACTION
in value:
82 if CONF_ACTIONS
in value:
84 "Cannot specify both 'action' and 'actions'. Please use 'actions' only."
86 value[CONF_ACTIONS] = value.pop(CONF_ACTION)
91 PLATFORM_SCHEMA = vol.All(
92 _backward_compat_schema,
93 cv.deprecated(CONF_HIDE_ENTITY),
94 script.make_script_schema(
98 CONF_ALIAS: cv.string,
99 vol.Optional(CONF_DESCRIPTION): cv.string,
100 vol.Optional(CONF_TRACE, default={}): TRACE_CONFIG_SCHEMA,
101 vol.Optional(CONF_INITIAL_STATE): cv.boolean,
102 vol.Optional(CONF_HIDE_ENTITY): cv.boolean,
103 vol.Required(CONF_TRIGGERS): cv.TRIGGER_SCHEMA,
104 vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
105 vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
106 vol.Optional(CONF_TRIGGER_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
107 vol.Required(CONF_ACTIONS): cv.SCRIPT_SCHEMA,
109 script.SCRIPT_MODE_SINGLE,
113 AUTOMATION_BLUEPRINT_SCHEMA = vol.All(
114 _backward_compat_schema, blueprint.schemas.BLUEPRINT_SCHEMA
121 raise_on_errors: bool,
122 warn_on_errors: bool,
123 ) -> AutomationConfig:
124 """Validate config item."""
126 raw_blueprint_inputs =
None
127 uses_blueprint =
False
128 with suppress(ValueError):
129 raw_config =
dict(config)
131 def _humanize(err: Exception, config: ConfigType) -> str:
132 """Humanize vol.Invalid, stringify other exceptions."""
133 if isinstance(err, vol.Invalid):
137 def _log_invalid_automation(
139 automation_name: str,
143 """Log an error about invalid automation."""
144 if not warn_on_errors:
149 "Blueprint '%s' generated invalid automation with inputs %s: %s",
150 blueprint_inputs.blueprint.name,
151 blueprint_inputs.inputs,
152 _humanize(err, config),
157 "%s %s and has been disabled: %s",
160 _humanize(err, config),
164 def _set_validation_status(
165 automation_config: AutomationConfig,
166 validation_status: ValidationStatus,
167 validation_error: Exception,
170 """Set validation status."""
172 validation_status = ValidationStatus.FAILED_BLUEPRINT
173 automation_config.validation_status = validation_status
174 automation_config.validation_error = _humanize(validation_error, config)
177 validation_status: ValidationStatus,
178 validation_error: Exception,
180 ) -> AutomationConfig:
181 """Try validating id, alias and description."""
184 automation_config.raw_blueprint_inputs = raw_blueprint_inputs
185 automation_config.raw_config = raw_config
186 _set_validation_status(
187 automation_config, validation_status, validation_error, config
189 return automation_config
191 if blueprint.is_blueprint_instance_config(config):
192 uses_blueprint =
True
195 blueprint_inputs = await blueprints.async_inputs_from_config(
198 except blueprint.BlueprintException
as err:
201 "Failed to generate automation from blueprint: %s",
206 return _minimal_config(ValidationStatus.FAILED_BLUEPRINT, err, config)
208 raw_blueprint_inputs = blueprint_inputs.config_with_inputs
211 config = blueprint_inputs.async_substitute()
212 raw_config =
dict(config)
213 except UndefinedSubstitution
as err:
216 "Blueprint '%s' failed to generate automation with inputs %s: %s",
217 blueprint_inputs.blueprint.name,
218 blueprint_inputs.inputs,
223 return _minimal_config(ValidationStatus.FAILED_BLUEPRINT, err, config)
225 automation_name =
"Unnamed automation"
226 if isinstance(config, Mapping):
227 if CONF_ALIAS
in config:
228 automation_name = f
"Automation with alias '{config[CONF_ALIAS]}'"
229 elif CONF_ID
in config:
230 automation_name = f
"Automation with ID '{config[CONF_ID]}'"
234 except vol.Invalid
as err:
235 _log_invalid_automation(err, automation_name,
"could not be validated", config)
238 return _minimal_config(ValidationStatus.FAILED_SCHEMA, err, config)
241 automation_config.raw_blueprint_inputs = raw_blueprint_inputs
242 automation_config.raw_config = raw_config
246 hass, validated_config[CONF_TRIGGERS]
252 _log_invalid_automation(
253 err, automation_name,
"failed to setup triggers", validated_config
257 _set_validation_status(
258 automation_config, ValidationStatus.FAILED_TRIGGERS, err, validated_config
260 return automation_config
262 if CONF_CONDITIONS
in validated_config:
265 hass, validated_config[CONF_CONDITIONS]
271 _log_invalid_automation(
272 err, automation_name,
"failed to setup conditions", validated_config
276 _set_validation_status(
278 ValidationStatus.FAILED_CONDITIONS,
282 return automation_config
285 automation_config[CONF_ACTIONS] = await script.async_validate_actions_config(
286 hass, validated_config[CONF_ACTIONS]
292 _log_invalid_automation(
293 err, automation_name,
"failed to setup actions", validated_config
297 _set_validation_status(
298 automation_config, ValidationStatus.FAILED_ACTIONS, err, validated_config
300 return automation_config
302 return automation_config
306 """What was changed in a config entry."""
308 FAILED_ACTIONS =
"failed_actions"
309 FAILED_BLUEPRINT =
"failed_blueprint"
310 FAILED_CONDITIONS =
"failed_conditions"
311 FAILED_SCHEMA =
"failed_schema"
312 FAILED_TRIGGERS =
"failed_triggers"
317 """Dummy class to allow adding attributes."""
319 raw_config: dict[str, Any] |
None =
None
320 raw_blueprint_inputs: dict[str, Any] |
None =
None
321 validation_status: ValidationStatus = ValidationStatus.OK
322 validation_error: str |
None =
None
327 config: dict[str, Any],
328 ) -> AutomationConfig |
None:
329 """Validate config item."""
332 except (vol.Invalid, HomeAssistantError):
339 config: dict[str, Any],
340 ) -> AutomationConfig |
None:
341 """Validate config item, called by EditAutomationConfigView."""
346 """Validate config."""
351 lambda x: x
is not None,
362 config[DOMAIN] = automations
ConfigType async_validate_config(HomeAssistant hass, ConfigType config)
Any _backward_compat_schema(Any|None value)
AutomationConfig|None _try_async_validate_config_item(HomeAssistant hass, dict[str, Any] config)
AutomationConfig|None async_validate_config_item(HomeAssistant hass, str config_key, dict[str, Any] config)
AutomationConfig _async_validate_config_item(HomeAssistant hass, ConfigType config, bool raise_on_errors, bool warn_on_errors)
blueprint.DomainBlueprints async_get_blueprints(HomeAssistant hass)
ConfigType async_validate_trigger_config(HomeAssistant hass, ConfigType config)
Iterable[tuple[str|None, ConfigType]] config_per_platform(ConfigType config, str domain)
ConfigType config_without_domain(ConfigType config, str domain)
str humanize_error(HomeAssistant hass, vol.Invalid validation_error, str domain, dict config, str|None link, int max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH)
list[ConfigType|Template] async_validate_conditions_config(HomeAssistant hass, list[ConfigType] conditions)