1 """Helpers for config validation using voluptuous."""
7 from collections.abc
import Callable, Hashable, Mapping
9 from contextvars
import ContextVar
10 from datetime
import (
12 datetime
as datetime_sys,
16 from enum
import Enum, StrEnum
19 from numbers
import Number
23 _GLOBAL_DEFAULT_TIMEOUT,
26 from typing
import Any, cast, overload
27 from urllib.parse
import urlparse
30 import voluptuous
as vol
31 import voluptuous_serialize
47 CONF_CONTINUE_ON_ERROR,
48 CONF_CONTINUE_ON_TIMEOUT,
57 CONF_ENTITY_NAMESPACE,
61 CONF_EVENT_DATA_TEMPLATE,
70 CONF_RESPONSE_VARIABLE,
76 CONF_SERVICE_DATA_TEMPLATE,
77 CONF_SERVICE_TEMPLATE,
78 CONF_SET_CONVERSATION_RESPONSE,
89 CONF_WAIT_FOR_TRIGGER,
101 DOMAIN
as HOMEASSISTANT_DOMAIN,
104 async_get_hass_or_none,
116 from .
import script_variables
as script_variables_helper, template
as template_helper
117 from .frame
import get_integration_logger
118 from .typing
import VolDictType, VolSchemaType
120 TIME_PERIOD_ERROR =
"offset {} should be format 'HH:MM', 'HH:MM:SS' or 'HH:MM:SS.F'"
124 """Raised when validation must happen in an executor thread."""
128 """Container which makes a HomeAssistant instance available to validators."""
130 hass: HomeAssistant |
None =
None
134 """Set when doing async friendly schema validation."""
138 """Return the HomeAssistant instance or None.
140 First tries core.async_get_hass_or_none, then _hass which is
141 set when doing async friendly schema validation.
146 _validating_async: ContextVar[bool] = ContextVar(
"_validating_async", default=
False)
147 """Set to True when doing async friendly schema validation."""
150 def not_async_friendly[**_P, _R](validator: Callable[_P, _R]) -> Callable[_P, _R]:
151 """Mark a validator as not async friendly.
153 This makes validation happen in an executor thread if validation is done by
154 async_validate, otherwise does nothing.
157 @functools.wraps(validator)
158 def _not_async_friendly(*args: _P.args, **kwargs: _P.kwargs) -> _R:
162 raise MustValidateInExecutor
163 return validator(*args, **kwargs)
165 return _not_async_friendly
169 """Valid URL protocol schema values."""
173 HOMEASSISTANT =
"homeassistant"
176 EXTERNAL_URL_PROTOCOL_SCHEMA_LIST = frozenset(
177 {UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS}
179 CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST = frozenset(
180 {UrlProtocolSchema.HOMEASSISTANT, UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS}
184 byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255))
185 small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
186 positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
187 positive_float = vol.All(vol.Coerce(float), vol.Range(min=0))
189 vol.Coerce(float), vol.Range(min=-90, max=90), msg=
"invalid latitude"
192 vol.Coerce(float), vol.Range(min=-180, max=180), msg=
"invalid longitude"
194 gps = vol.ExactSequence([latitude, longitude])
195 sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE))
196 port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
200 """Validate it's a safe path."""
201 if not isinstance(value, str):
202 raise vol.Invalid(
"Expected a string")
206 except ValueError
as err:
207 raise vol.Invalid(
"Invalid path")
from err
215 """Validate that at least one key exists."""
219 """Test keys exist in dict."""
220 if not isinstance(obj, dict):
221 raise vol.Invalid(
"expected dictionary")
223 if not key_set.isdisjoint(obj):
225 expected =
", ".join(
str(k)
for k
in keys)
226 raise vol.Invalid(f
"must contain at least one of {expected}.")
232 """Validate that zero keys exist or one key exists."""
235 """Test zero keys exist or one key exists in dict."""
236 if not isinstance(obj, dict):
237 raise vol.Invalid(
"expected dictionary")
239 if len(set(keys) & set(obj)) > 1:
240 expected =
", ".join(
str(k)
for k
in keys)
241 raise vol.Invalid(f
"must contain at most one of {expected}.")
248 """Validate and coerce a boolean value."""
249 if isinstance(value, bool):
251 if isinstance(value, str):
252 value = value.lower().strip()
253 if value
in (
"1",
"true",
"yes",
"on",
"enable"):
255 if value
in (
"0",
"false",
"no",
"off",
"disable"):
257 elif isinstance(value, Number):
260 raise vol.Invalid(f
"invalid boolean value {value}")
264 """Validate result contains only whitespace."""
265 if isinstance(value, str)
and (value ==
"" or value.isspace()):
268 raise vol.Invalid(f
"contains non-whitespace: {value}")
273 """Validate that value is a real device."""
277 except OSError
as err:
278 raise vol.Invalid(f
"No device at {value} found")
from err
282 """Validate that the value is a string that matches a regex."""
283 compiled = re.compile(regex)
285 def validator(value: Any) -> str:
286 """Validate that value matches the given regex."""
287 if not isinstance(value, str):
288 raise vol.Invalid(f
"not a string value: {value}")
290 if not compiled.match(value):
292 f
"value {value} does not match regular expression {compiled.pattern}"
301 """Validate that a string is a valid regular expression."""
303 r = re.compile(value)
304 except TypeError
as err:
306 f
"value {value} is of the wrong type for a regular expression"
308 except re.error
as err:
309 raise vol.Invalid(f
"value {value} is not a valid regular expression")
from err
315 """Validate that the value is an existing file."""
317 raise vol.Invalid(
"None is not file")
318 file_in = os.path.expanduser(
str(value))
320 if not os.path.isfile(file_in):
321 raise vol.Invalid(
"not a file")
322 if not os.access(file_in, os.R_OK):
323 raise vol.Invalid(
"file not readable")
329 """Validate that the value is an existing dir."""
331 raise vol.Invalid(
"not a directory")
332 dir_in = os.path.expanduser(
str(value))
334 if not os.path.isdir(dir_in):
335 raise vol.Invalid(
"not a directory")
336 if not os.access(dir_in, os.R_OK):
337 raise vol.Invalid(
"directory not readable")
354 """Wrap value in list if it is not one."""
357 return cast(
"list[_T]", value)
if isinstance(value, list)
else [value]
361 """Validate Entity ID."""
362 str_value =
string(value).lower()
366 raise vol.Invalid(f
"Entity ID {value} is an invalid entity ID")
370 """Validate Entity specified by entity_id or uuid."""
371 with contextlib.suppress(vol.Invalid):
373 with contextlib.suppress(vol.Invalid):
375 raise vol.Invalid(f
"Entity {value} is neither a valid entity ID nor a valid UUID")
378 def _entity_ids(value: str | list, allow_uuid: bool) -> list[str]:
379 """Help validate entity IDs or UUIDs."""
381 raise vol.Invalid(
"Entity IDs cannot be None")
382 if isinstance(value, str):
383 value = [ent_id.strip()
for ent_id
in value.split(
",")]
385 validator = entity_id_or_uuid
if allow_uuid
else entity_id
386 return [validator(ent_id)
for ent_id
in value]
390 """Validate Entity IDs."""
395 """Validate entities specified by entity IDs or UUIDs."""
399 comp_entity_ids = vol.Any(
400 vol.All(vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), entity_ids
404 comp_entity_ids_or_uuids = vol.Any(
405 vol.All(vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)),
411 """Validate a top level config key with an optional label and return the domain.
413 A domain is separated from a label by one or more spaces, empty labels are not
418 'hue 1' returns 'hue'
419 'hue 1' returns 'hue'
424 if not isinstance(config_key, str):
425 raise vol.Invalid(
"invalid domain", path=[config_key])
427 parts = config_key.partition(
" ")
428 _domain = parts[0]
if parts[2].strip(
" ")
else config_key
429 if not _domain
or _domain.strip(
" ") != _domain:
430 raise vol.Invalid(
"invalid domain", path=[config_key])
436 """Validate that entity belong to domain."""
440 """Test if entity domain is domain."""
441 validated = ent_domain(value)
442 if len(validated) != 1:
443 raise vol.Invalid(f
"Expected exactly 1 entity, got {len(validated)}")
450 """Validate that entities belong to domain."""
451 if isinstance(domain, str):
453 def check_invalid(val: str) -> bool:
458 def check_invalid(val: str) -> bool:
459 return val
not in domain
461 def validate(values: str | list) -> list[str]:
462 """Test if entity domain is domain."""
464 for ent_id
in values:
467 f
"Entity ID '{ent_id}' does not belong to domain '{domain}'"
474 def enum(enumClass: type[Enum]) -> vol.All:
475 """Create validator for specified enum."""
476 return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__)
481 str_value =
str(value)
486 raise vol.Invalid(
'Icons should be specified in the form "prefix:name"')
489 _COLOR_HEX = re.compile(
r"^#[0-9A-F]{6}$", re.IGNORECASE)
493 """Validate a hex color code."""
494 str_value =
str(value)
496 if not _COLOR_HEX.match(str_value):
497 raise vol.Invalid(
"Color should be in the format #RRGGBB")
502 _TIME_PERIOD_DICT_KEYS = (
"days",
"hours",
"minutes",
"seconds",
"milliseconds")
504 time_period_dict = vol.All(
508 "days": vol.Coerce(float),
509 "hours": vol.Coerce(float),
510 "minutes": vol.Coerce(float),
511 "seconds": vol.Coerce(float),
512 "milliseconds": vol.Coerce(float),
520 def time(value: Any) -> time_sys:
521 """Validate and transform a time."""
522 if isinstance(value, time_sys):
526 time_val = dt_util.parse_time(value)
527 except TypeError
as err:
528 raise vol.Invalid(
"Not a parseable type")
from err
531 raise vol.Invalid(f
"Invalid time specified: {value}")
536 def date(value: Any) -> date_sys:
537 """Validate and transform a date."""
538 if isinstance(value, date_sys):
542 date_val = dt_util.parse_date(value)
543 except TypeError
as err:
544 raise vol.Invalid(
"Not a parseable type")
from err
547 raise vol.Invalid(
"Could not parse date")
553 """Validate and transform time offset."""
554 if isinstance(value, int):
555 raise vol.Invalid(
"Make sure you wrap time values in quotes")
556 if not isinstance(value, str):
557 raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
559 negative_offset =
False
560 if value.startswith(
"-"):
561 negative_offset =
True
563 elif value.startswith(
"+"):
566 parsed = value.split(
":")
567 if len(parsed)
not in (2, 3):
568 raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
570 hour =
int(parsed[0])
571 minute =
int(parsed[1])
573 second =
float(parsed[2])
576 except ValueError
as err:
577 raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
from err
579 offset =
timedelta(hours=hour, minutes=minute, seconds=second)
588 """Validate and transform seconds to a time offset."""
591 except (ValueError, TypeError)
as err:
592 raise vol.Invalid(f
"Expected seconds, got {value}")
from err
595 time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict)
598 def match_all[_T](value: _T) -> _T:
599 """Validate that matches all values."""
604 """Validate timedelta is positive."""
606 raise vol.Invalid(
"Time period should be positive")
610 positive_time_period_dict = vol.All(time_period_dict, positive_timedelta)
611 positive_time_period = vol.All(time_period, positive_timedelta)
614 def remove_falsy[_T](value: list[_T]) -> list[_T]:
615 """Remove falsy values from a list."""
616 return [v
for v
in value
if v]
620 """Validate service."""
622 str_value =
string(value).lower()
626 raise vol.Invalid(f
"Service {value} does not match format <domain>.<name>")
630 """Validate value is a valid slug."""
632 raise vol.Invalid(
"Slug should not be None")
633 str_value =
str(value)
634 slg = util_slugify(str_value)
637 raise vol.Invalid(f
"invalid slug {value} (try {slg})")
641 value_schema: dict | Callable, *, slug_validator: Callable[[Any], str] = slug
643 """Ensure dicts have slugs as keys.
645 Replacement of vol.Schema({cv.slug: value_schema}) to prevent misleading
646 "Extra keys" errors from voluptuous.
648 schema = vol.Schema({str: value_schema})
650 def verify(value: dict) -> dict:
651 """Validate all keys are slugs and then the value_schema."""
652 if not isinstance(value, dict):
653 raise vol.Invalid(
"expected dictionary")
658 return cast(dict, schema(value))
664 """Coerce a value to a slug."""
666 raise vol.Invalid(
"Slug should not be None")
667 slg = util_slugify(
str(value))
670 raise vol.Invalid(f
"Unable to slugify {value}")
674 """Coerce value to string, except for None."""
676 raise vol.Invalid(
"string value is None")
681 or type(value)
is NodeStrClass
682 or isinstance(value, str)
686 if isinstance(value, template_helper.ResultWrapper):
687 value = value.render_result
689 elif isinstance(value, (list, dict)):
690 raise vol.Invalid(
"value should be a string")
696 """Validate that the value is a string without HTML."""
698 regex = re.compile(
r"<[a-z].*?>", re.IGNORECASE)
699 if regex.search(value):
700 raise vol.Invalid(
"the string should not contain HTML")
705 """Validate and transform temperature unit."""
706 value =
str(value).upper()
708 return UnitOfTemperature.CELSIUS
710 return UnitOfTemperature.FAHRENHEIT
711 raise vol.Invalid(
"invalid temperature unit (expected C or F)")
714 def template(value: Any |
None) -> template_helper.Template:
715 """Validate a jinja2 template."""
717 raise vol.Invalid(
"template value is None")
718 if isinstance(value, (list, dict, template_helper.Template)):
719 raise vol.Invalid(
"template value should be a string")
722 from .frame
import ReportBehavior, report_usage
726 "validates schema outside the event loop, "
727 "which will stop working in HA Core 2025.10"
729 core_behavior=ReportBehavior.LOG,
732 template_value = template_helper.Template(
str(value), hass)
735 template_value.ensure_valid()
736 except TemplateError
as ex:
737 raise vol.Invalid(f
"invalid template ({ex})")
from ex
738 return template_value
742 """Validate a dynamic (non static) jinja2 template."""
744 raise vol.Invalid(
"template value is None")
745 if isinstance(value, (list, dict, template_helper.Template)):
746 raise vol.Invalid(
"template value should be a string")
747 if not template_helper.is_template_string(
str(value)):
748 raise vol.Invalid(
"template value does not contain a dynamic template")
751 from .frame
import ReportBehavior, report_usage
755 "validates schema outside the event loop, "
756 "which will stop working in HA Core 2025.10"
758 core_behavior=ReportBehavior.LOG,
761 template_value = template_helper.Template(
str(value), hass)
764 template_value.ensure_valid()
765 except TemplateError
as ex:
766 raise vol.Invalid(f
"invalid template ({ex})")
from ex
767 return template_value
771 """Validate a complex jinja2 template."""
772 if isinstance(value, list):
773 return_list = value.copy()
774 for idx, element
in enumerate(return_list):
777 if isinstance(value, dict):
780 for key, element
in value.items()
782 if isinstance(value, str)
and template_helper.is_template_string(value):
789 """Do basic validation of a positive time period expressed as a templated dict."""
790 if not isinstance(value, dict)
or not value:
791 raise vol.Invalid(
"template should be a dict")
792 for key, element
in value.items():
793 if not isinstance(key, str):
794 raise vol.Invalid(
"key should be a string")
795 if not template_helper.is_template_string(key):
796 vol.In(_TIME_PERIOD_DICT_KEYS)(key)
797 if not isinstance(element, str)
or (
798 isinstance(element, str)
and not template_helper.is_template_string(element)
800 vol.All(vol.Coerce(float), vol.Range(min=0))(element)
804 positive_time_period_template = vol.Any(
805 positive_time_period, dynamic_template, _positive_time_period_template_complex
810 """Validate datetime."""
811 if isinstance(value, datetime_sys):
815 date_val = dt_util.parse_datetime(value)
820 raise vol.Invalid(f
"Invalid datetime specified: {value}")
826 """Validate timezone."""
827 if dt_util.get_time_zone(value)
is not None:
830 "Invalid time zone passed in. Valid options can be found here: "
831 "http://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
835 weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)])
839 """Validate timeout float > 0.0.
841 None coerced to socket._GLOBAL_DEFAULT_TIMEOUT bare object.
844 return _GLOBAL_DEFAULT_TIMEOUT
846 float_value =
float(value)
847 if float_value > 0.0:
849 except Exception
as err:
850 raise vol.Invalid(f
"Invalid socket timeout: {err}")
from err
851 raise vol.Invalid(
"Invalid socket timeout value. float > 0.0 required.")
856 _schema_list: frozenset[UrlProtocolSchema] = EXTERNAL_URL_PROTOCOL_SCHEMA_LIST,
858 """Validate an URL."""
861 if urlparse(url_in).scheme
in _schema_list:
862 return cast(str, vol.Schema(vol.Url())(url_in))
864 raise vol.Invalid(
"invalid url")
868 """Validate an URL that allows the homeassistant schema."""
869 return url(value, CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST)
873 """Validate a url without a path."""
876 if urlparse(url_in).path
not in (
"",
"/"):
877 raise vol.Invalid(
"url is not allowed to have a path component")
883 """Validate an x10 address."""
884 regex = re.compile(
r"([A-Pa-p]{1})(?:[2-9]|1[0-6]?)$")
885 if not regex.match(value):
886 raise vol.Invalid(
"Invalid X10 Address")
887 return str(value).lower()
891 """Validate a v4 UUID in hex format."""
893 result =
UUID(value, version=4)
894 except (ValueError, AttributeError, TypeError)
as error:
895 raise vol.Invalid(
"Invalid Version4 UUID", error_message=
str(error))
from error
897 if result.hex != value.lower():
899 raise vol.Invalid(
"Invalid Version4 UUID")
904 _FAKE_UUID_4_HEX = re.compile(
r"^[0-9a-f]{32}$")
908 """Validate a fake v4 UUID generated by random_uuid_hex."""
910 if not _FAKE_UUID_4_HEX.match(value):
911 raise vol.Invalid(
"Invalid UUID")
912 except TypeError
as exc:
913 raise vol.Invalid(
"Invalid UUID")
from exc
914 return cast(str, value)
918 """Ensure that input is a list or make one from comma-separated string."""
919 if isinstance(value, str):
920 return [member.strip()
for member
in value.split(
",")]
925 """Multi select validator returning list of selected values."""
928 """Initialize multi select."""
932 """Validate input."""
933 if not isinstance(selected, list):
934 raise vol.Invalid(
"Not a list")
936 for value
in selected:
937 if value
not in self.
optionsoptions:
938 raise vol.Invalid(f
"{value} is not a valid option")
945 replacement_key: str |
None,
947 raise_if_present: bool,
948 option_removed: bool,
949 ) -> Callable[[dict], dict]:
950 """Log key as deprecated and provide a replacement (if exists) or fail.
953 - Outputs or throws the appropriate deprecation warning if key is detected
954 - Outputs or throws the appropriate error if key is detected
955 and removed from support
956 - Processes schema moving the value from key to replacement_key
957 - Processes schema changing nothing if only replacement_key provided
958 - No warning if only replacement_key provided
959 - No warning if neither key nor replacement_key are provided
960 - Adds replacement_key with default value in this case
963 def validator(config: dict) -> dict:
964 """Check if key is in config and log warning or error."""
967 level = logging.ERROR
968 option_status =
"has been removed"
970 level = logging.WARNING
971 option_status =
"is deprecated"
975 f
"near {config.__config_file__}"
976 f
":{config.__line__} "
978 except AttributeError:
980 arguments: tuple[str, ...]
982 warning =
"The '%s' option %s%s, please replace it with '%s'"
983 arguments = (key, near, option_status, replacement_key)
986 "The '%s' option %s%s, please remove it from your configuration"
988 arguments = (key, near, option_status)
991 raise vol.Invalid(warning % arguments)
995 if replacement_key
or option_removed:
1002 keys.append(replacement_key)
1003 if value
is not None and (
1004 replacement_key
not in config
or default == config.get(replacement_key)
1006 config[replacement_key] = value
1015 replacement_key: str |
None =
None,
1016 default: Any |
None =
None,
1017 raise_if_present: bool |
None =
False,
1018 ) -> Callable[[dict], dict]:
1019 """Log key as deprecated and provide a replacement (if exists).
1022 - Outputs the appropriate deprecation warning if key is detected
1023 or raises an exception
1024 - Processes schema moving the value from key to replacement_key
1025 - Processes schema changing nothing if only replacement_key provided
1026 - No warning if only replacement_key provided
1027 - No warning if neither key nor replacement_key are provided
1028 - Adds replacement_key with default value in this case
1032 replacement_key=replacement_key,
1034 raise_if_present=raise_if_present
or False,
1035 option_removed=
False,
1041 default: Any |
None =
None,
1042 raise_if_present: bool |
None =
True,
1043 ) -> Callable[[dict], dict]:
1044 """Log key as deprecated and fail the config validation.
1047 - Outputs the appropriate error if key is detected and removed from
1048 support or raises an exception.
1052 replacement_key=
None,
1054 raise_if_present=raise_if_present
or False,
1055 option_removed=
True,
1061 value_schemas: dict[Hashable, VolSchemaType | Callable[[Any], dict[str, Any]]],
1062 default_schema: VolSchemaType |
None =
None,
1063 default_description: str |
None =
None,
1064 ) -> Callable[[Any], dict[Hashable, Any]]:
1065 """Create a validator that validates based on a value for specific key.
1067 This gives better error messages.
1070 def key_value_validator(value: Any) -> dict[Hashable, Any]:
1071 if not isinstance(value, dict):
1072 raise vol.Invalid(
"Expected a dictionary")
1074 key_value = value.get(key)
1076 if isinstance(key_value, Hashable)
and key_value
in value_schemas:
1077 return cast(dict[Hashable, Any], value_schemas[key_value](value))
1080 with contextlib.suppress(vol.Invalid):
1083 alternatives =
", ".join(
str(alternative)
for alternative
in value_schemas)
1084 if default_description:
1085 alternatives = f
"{alternatives}, {default_description}"
1087 f
"Unexpected value for {key}: '{key_value}'. Expected {alternatives}"
1090 return key_value_validator
1096 def key_dependency[_KT: Hashable, _VT](
1097 key: Hashable, dependency: Hashable
1098 ) -> Callable[[dict[_KT, _VT]], dict[_KT, _VT]]:
1099 """Validate that all dependencies exist for key."""
1101 def validator(value: dict[_KT, _VT]) -> dict[_KT, _VT]:
1102 """Test dependencies."""
1103 if not isinstance(value, dict):
1104 raise vol.Invalid(
"key dependencies require a dict")
1105 if key
in value
and dependency
not in value:
1107 f
'dependency violation - key "{key}" requires '
1108 f
'key "{dependency}" to exist'
1117 """Serialize additional types for voluptuous_serialize."""
1122 """Serialize additional types for voluptuous_serialize."""
1123 from homeassistant
import data_entry_flow
1125 from .
import selector
1127 if schema
is positive_time_period_dict:
1128 return {
"type":
"positive_time_period_dict"}
1130 if schema
is string:
1131 return {
"type":
"string"}
1133 if schema
is boolean:
1134 return {
"type":
"boolean"}
1137 if not allow_section:
1138 raise ValueError(
"Nesting expandable sections is not supported")
1140 "type":
"expandable",
1141 "schema": voluptuous_serialize.convert(
1143 custom_serializer=functools.partial(
1144 _custom_serializer, allow_section=
False
1147 "expanded":
not schema.options[
"collapsed"],
1150 if isinstance(schema, multi_select):
1151 return {
"type":
"multi_select",
"options": schema.options}
1154 return schema.serialize()
1156 return voluptuous_serialize.UNSUPPORTED
1160 """Expand boolean condition shorthand notations."""
1162 if not isinstance(value, dict)
or CONF_CONDITIONS
in value:
1165 for key, schema
in (
1166 (
"and", AND_CONDITION_SHORTHAND_SCHEMA),
1167 (
"or", OR_CONDITION_SHORTHAND_SCHEMA),
1168 (
"not", NOT_CONDITION_SHORTHAND_SCHEMA),
1173 CONF_CONDITION: key,
1174 CONF_CONDITIONS: value[key],
1175 **{k: value[k]
for k
in value
if k != key},
1177 except vol.MultipleInvalid:
1180 if isinstance(value.get(CONF_CONDITION), list):
1184 CONF_CONDITION:
"and",
1185 CONF_CONDITIONS: value[CONF_CONDITION],
1186 **{k: value[k]
for k
in value
if k != CONF_CONDITION},
1188 except vol.MultipleInvalid:
1196 """Return a config schema which logs if there are configuration parameters."""
1198 def validator(config: dict) -> dict:
1199 if config_domain := config.get(domain):
1202 "The %s integration does not support any configuration parameters, "
1203 "got %s. Please remove the configuration parameters from your "
1217 translation_key: str,
1218 translation_placeholders: dict[str, str],
1219 ) -> Callable[[dict], dict]:
1220 """Return a config schema which logs if attempted to setup from YAML."""
1222 def raise_issue() -> None:
1224 from .issue_registry
import IssueSeverity, async_create_issue
1227 with contextlib.suppress(HomeAssistantError):
1231 HOMEASSISTANT_DOMAIN,
1232 f
"{issue_base}_{domain}",
1234 issue_domain=domain,
1235 severity=IssueSeverity.ERROR,
1236 translation_key=translation_key,
1237 translation_placeholders={
"domain": domain} | translation_placeholders,
1240 def validator(config: dict) -> dict:
1241 if domain
in config:
1244 "The %s integration does not support YAML setup, please remove it "
1245 "from your configuration file"
1256 """Return a config schema which logs if attempted to setup from YAML.
1258 Use this when an integration's __init__.py defines setup or async_setup
1259 but setup from yaml is not supported.
1264 "config_entry_only",
1265 "config_entry_only",
1266 {
"add_integration": f
"/config/integrations/dashboard/add?domain={domain}"},
1271 """Return a config schema which logs if attempted to setup from YAML.
1273 Use this when an integration's __init__.py defines setup or async_setup
1274 but setup from the integration key is not supported.
1285 PLATFORM_SCHEMA = vol.Schema(
1287 vol.Required(CONF_PLATFORM): string,
1288 vol.Optional(CONF_ENTITY_NAMESPACE): string,
1289 vol.Optional(CONF_SCAN_INTERVAL): time_period,
1293 PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
1295 ENTITY_SERVICE_FIELDS: VolDictType = {
1299 vol.Optional(ATTR_ENTITY_ID): vol.Any(
1300 comp_entity_ids, dynamic_template, vol.All(list, template_complex)
1302 vol.Optional(ATTR_DEVICE_ID): vol.Any(
1303 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1305 vol.Optional(ATTR_AREA_ID): vol.Any(
1306 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1308 vol.Optional(ATTR_FLOOR_ID): vol.Any(
1309 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1311 vol.Optional(ATTR_LABEL_ID): vol.Any(
1312 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1316 TARGET_SERVICE_FIELDS = {
1322 vol.Optional(ATTR_ENTITY_ID): vol.Any(
1323 comp_entity_ids_or_uuids, dynamic_template, vol.All(list, template_complex)
1325 vol.Optional(ATTR_DEVICE_ID): vol.Any(
1326 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1328 vol.Optional(ATTR_AREA_ID): vol.Any(
1329 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1331 vol.Optional(ATTR_FLOOR_ID): vol.Any(
1332 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1334 vol.Optional(ATTR_LABEL_ID): vol.Any(
1335 ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)])
1344 """Check if the passed validator is an entity schema validator.
1346 The validator must be either of:
1347 - A validator returned by cv._make_entity_service_schema
1348 - A validator returned by cv._make_entity_service_schema, wrapped in a vol.Schema
1349 - A validator returned by cv._make_entity_service_schema, wrapped in a vol.All
1352 if hasattr(validator,
"_entity_service_schema"):
1354 if isinstance(validator, (vol.All)):
1356 if isinstance(validator, (vol.Schema)):
1363 """Create an entity service schema."""
1364 validator = vol.All(
1368 vol.Remove(
"metadata"): dict,
1370 **ENTITY_SERVICE_FIELDS,
1374 _HAS_ENTITY_SERVICE_FIELD,
1376 setattr(validator,
"_entity_service_schema",
True)
1384 schema: dict |
None, *, extra: int = vol.PREVENT_EXTRA
1386 """Create an entity service schema."""
1387 if not schema
and extra == vol.PREVENT_EXTRA:
1391 return BASE_ENTITY_SCHEMA
1395 SCRIPT_CONVERSATION_RESPONSE_SCHEMA = vol.Any(template,
None)
1398 SCRIPT_VARIABLES_SCHEMA = vol.All(
1399 vol.Schema({str: template_complex}),
1401 lambda val: script_variables_helper.ScriptVariables(val),
1406 """Validate a script action."""
1407 if not isinstance(value, dict):
1408 raise vol.Invalid(
"expected dictionary")
1412 except ValueError
as err:
1413 raise vol.Invalid(
str(err))
from err
1415 return ACTION_TYPE_SCHEMAS[action](value)
1418 SCRIPT_SCHEMA = vol.All(ensure_list, [script_action])
1420 SCRIPT_ACTION_BASE_SCHEMA: VolDictType = {
1421 vol.Optional(CONF_ALIAS): string,
1422 vol.Optional(CONF_CONTINUE_ON_ERROR): boolean,
1423 vol.Optional(CONF_ENABLED): vol.Any(boolean, template),
1426 EVENT_SCHEMA = vol.Schema(
1428 **SCRIPT_ACTION_BASE_SCHEMA,
1429 vol.Required(CONF_EVENT): string,
1430 vol.Optional(CONF_EVENT_DATA): vol.All(dict, template_complex),
1431 vol.Optional(CONF_EVENT_DATA_TEMPLATE): vol.All(dict, template_complex),
1437 """Backward compatibility for service schemas."""
1439 if not isinstance(value, dict):
1443 if CONF_SERVICE
in value:
1444 if CONF_ACTION
in value:
1446 "Cannot specify both 'service' and 'action'. Please use 'action' only."
1448 value[CONF_ACTION] = value.pop(CONF_SERVICE)
1453 SERVICE_SCHEMA = vol.All(
1454 _backward_compat_service_schema,
1457 **SCRIPT_ACTION_BASE_SCHEMA,
1458 vol.Exclusive(CONF_ACTION,
"service name"): vol.Any(
1459 service, dynamic_template
1461 vol.Exclusive(CONF_SERVICE_TEMPLATE,
"service name"): vol.Any(
1462 service, dynamic_template
1464 vol.Optional(CONF_SERVICE_DATA): vol.Any(
1465 template, vol.All(dict, template_complex)
1467 vol.Optional(CONF_SERVICE_DATA_TEMPLATE): vol.Any(
1468 template, vol.All(dict, template_complex)
1470 vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
1471 vol.Optional(CONF_TARGET): vol.Any(TARGET_SERVICE_FIELDS, dynamic_template),
1472 vol.Optional(CONF_RESPONSE_VARIABLE): str,
1474 vol.Remove(
"metadata"): dict,
1480 NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any(
1482 vol.All(str,
entity_domain([
"input_number",
"number",
"sensor",
"zone"])),
1485 CONDITION_BASE_SCHEMA: VolDictType = {
1486 vol.Optional(CONF_ALIAS): string,
1487 vol.Optional(CONF_ENABLED): vol.Any(boolean, template),
1490 NUMERIC_STATE_CONDITION_SCHEMA = vol.All(
1493 **CONDITION_BASE_SCHEMA,
1494 vol.Required(CONF_CONDITION):
"numeric_state",
1495 vol.Required(CONF_ENTITY_ID): entity_ids_or_uuids,
1496 vol.Optional(CONF_ATTRIBUTE): str,
1497 CONF_BELOW: NUMERIC_STATE_THRESHOLD_SCHEMA,
1498 CONF_ABOVE: NUMERIC_STATE_THRESHOLD_SCHEMA,
1499 vol.Optional(CONF_VALUE_TEMPLATE): template,
1505 STATE_CONDITION_BASE_SCHEMA = {
1506 **CONDITION_BASE_SCHEMA,
1507 vol.Required(CONF_CONDITION):
"state",
1508 vol.Required(CONF_ENTITY_ID): entity_ids_or_uuids,
1509 vol.Optional(CONF_MATCH, default=ENTITY_MATCH_ALL): vol.All(
1510 vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_ANY)
1512 vol.Optional(CONF_ATTRIBUTE): str,
1513 vol.Optional(CONF_FOR): positive_time_period_template,
1516 vol.Optional(
"from"): str,
1519 STATE_CONDITION_STATE_SCHEMA = vol.Schema(
1521 **STATE_CONDITION_BASE_SCHEMA,
1522 vol.Required(CONF_STATE): vol.Any(str, [str]),
1526 STATE_CONDITION_ATTRIBUTE_SCHEMA = vol.Schema(
1528 **STATE_CONDITION_BASE_SCHEMA,
1529 vol.Required(CONF_STATE): match_all,
1535 """Validate a state condition."""
1536 if not isinstance(value, dict):
1537 raise vol.Invalid(
"Expected a dictionary")
1539 if CONF_ATTRIBUTE
in value:
1544 return key_dependency(
"for",
"state")(validated)
1547 SUN_CONDITION_SCHEMA = vol.All(
1550 **CONDITION_BASE_SCHEMA,
1551 vol.Required(CONF_CONDITION):
"sun",
1552 vol.Optional(
"before"): sun_event,
1553 vol.Optional(
"before_offset"): time_period,
1554 vol.Optional(
"after"): vol.All(
1555 vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)
1557 vol.Optional(
"after_offset"): time_period,
1563 TEMPLATE_CONDITION_SCHEMA = vol.Schema(
1565 **CONDITION_BASE_SCHEMA,
1566 vol.Required(CONF_CONDITION):
"template",
1567 vol.Required(CONF_VALUE_TEMPLATE): template,
1571 TIME_CONDITION_SCHEMA = vol.All(
1574 **CONDITION_BASE_SCHEMA,
1575 vol.Required(CONF_CONDITION):
"time",
1576 vol.Optional(
"before"): vol.Any(
1577 time, vol.All(str,
entity_domain([
"input_datetime",
"time",
"sensor"]))
1579 vol.Optional(
"after"): vol.Any(
1580 time, vol.All(str,
entity_domain([
"input_datetime",
"time",
"sensor"]))
1582 vol.Optional(
"weekday"): weekdays,
1588 TRIGGER_CONDITION_SCHEMA = vol.Schema(
1590 **CONDITION_BASE_SCHEMA,
1591 vol.Required(CONF_CONDITION):
"trigger",
1592 vol.Required(CONF_ID): vol.All(ensure_list, [string]),
1596 ZONE_CONDITION_SCHEMA = vol.Schema(
1598 **CONDITION_BASE_SCHEMA,
1599 vol.Required(CONF_CONDITION):
"zone",
1600 vol.Required(CONF_ENTITY_ID): entity_ids,
1601 vol.Required(
"zone"): entity_ids,
1604 vol.Optional(
"event"): vol.Any(
"enter",
"leave"),
1608 AND_CONDITION_SCHEMA = vol.Schema(
1610 **CONDITION_BASE_SCHEMA,
1611 vol.Required(CONF_CONDITION):
"and",
1612 vol.Required(CONF_CONDITIONS): vol.All(
1620 AND_CONDITION_SHORTHAND_SCHEMA = vol.Schema(
1622 **CONDITION_BASE_SCHEMA,
1623 vol.Required(
"and"): vol.All(
1631 OR_CONDITION_SCHEMA = vol.Schema(
1633 **CONDITION_BASE_SCHEMA,
1634 vol.Required(CONF_CONDITION):
"or",
1635 vol.Required(CONF_CONDITIONS): vol.All(
1643 OR_CONDITION_SHORTHAND_SCHEMA = vol.Schema(
1645 **CONDITION_BASE_SCHEMA,
1646 vol.Required(
"or"): vol.All(
1654 NOT_CONDITION_SCHEMA = vol.Schema(
1656 **CONDITION_BASE_SCHEMA,
1657 vol.Required(CONF_CONDITION):
"not",
1658 vol.Required(CONF_CONDITIONS): vol.All(
1666 NOT_CONDITION_SHORTHAND_SCHEMA = vol.Schema(
1668 **CONDITION_BASE_SCHEMA,
1669 vol.Required(
"not"): vol.All(
1677 DEVICE_CONDITION_BASE_SCHEMA = vol.Schema(
1679 **CONDITION_BASE_SCHEMA,
1680 vol.Required(CONF_CONDITION):
"device",
1681 vol.Required(CONF_DEVICE_ID): str,
1682 vol.Required(CONF_DOMAIN): str,
1683 vol.Remove(
"metadata"): dict,
1687 DEVICE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
1689 dynamic_template_condition_action = vol.All(
1693 CONF_VALUE_TEMPLATE: config,
1694 CONF_CONDITION:
"template",
1698 CONDITION_SHORTHAND_SCHEMA = vol.Schema(
1700 **CONDITION_BASE_SCHEMA,
1701 vol.Required(CONF_CONDITION): vol.All(
1709 CONDITION_SCHEMA: vol.Schema = vol.Schema(
1712 expand_condition_shorthand,
1716 "and": AND_CONDITION_SCHEMA,
1717 "device": DEVICE_CONDITION_SCHEMA,
1718 "not": NOT_CONDITION_SCHEMA,
1719 "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA,
1720 "or": OR_CONDITION_SCHEMA,
1721 "state": STATE_CONDITION_SCHEMA,
1722 "sun": SUN_CONDITION_SCHEMA,
1723 "template": TEMPLATE_CONDITION_SCHEMA,
1724 "time": TIME_CONDITION_SCHEMA,
1725 "trigger": TRIGGER_CONDITION_SCHEMA,
1726 "zone": ZONE_CONDITION_SCHEMA,
1730 dynamic_template_condition_action,
1734 CONDITIONS_SCHEMA = vol.All(ensure_list, [CONDITION_SCHEMA])
1736 dynamic_template_condition_action = vol.All(
1739 {**CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): dynamic_template}
1743 CONF_VALUE_TEMPLATE: config[CONF_CONDITION],
1744 CONF_CONDITION:
"template",
1749 CONDITION_ACTION_SCHEMA: vol.Schema = vol.Schema(
1751 expand_condition_shorthand,
1755 "and": AND_CONDITION_SCHEMA,
1756 "device": DEVICE_CONDITION_SCHEMA,
1757 "not": NOT_CONDITION_SCHEMA,
1758 "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA,
1759 "or": OR_CONDITION_SCHEMA,
1760 "state": STATE_CONDITION_SCHEMA,
1761 "sun": SUN_CONDITION_SCHEMA,
1762 "template": TEMPLATE_CONDITION_SCHEMA,
1763 "time": TIME_CONDITION_SCHEMA,
1764 "trigger": TRIGGER_CONDITION_SCHEMA,
1765 "zone": ZONE_CONDITION_SCHEMA,
1767 dynamic_template_condition_action,
1768 "a list of conditions or a valid template",
1775 """Rewrite trigger `trigger` to `platform`.
1777 `platform` has been renamed to `trigger` in user documentation and in the automation
1778 editor. The Python trigger implementation still uses `platform`, so we need to
1779 rename `trigger` to `platform.
1782 if not isinstance(value, Mapping):
1786 if CONF_TRIGGER
in value:
1787 if CONF_PLATFORM
in value:
1789 "Cannot specify both 'platform' and 'trigger'. Please use 'trigger' only."
1792 value[CONF_PLATFORM] = value.pop(CONF_TRIGGER)
1793 elif CONF_PLATFORM
not in value:
1794 raise vol.Invalid(
"required key not provided", [CONF_TRIGGER])
1799 TRIGGER_BASE_SCHEMA = vol.Schema(
1801 vol.Optional(CONF_ALIAS): str,
1802 vol.Required(CONF_PLATFORM): str,
1803 vol.Optional(CONF_ID): str,
1804 vol.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA,
1805 vol.Optional(CONF_ENABLED): vol.Any(boolean, template),
1810 _base_trigger_validator_schema = TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
1814 """Flatten trigger arrays containing 'triggers:' sublists into a single list of triggers."""
1817 if CONF_TRIGGERS
in t
and len(t) == 1:
1819 flatlist.extend(triggerlist)
1833 TRIGGER_SCHEMA = vol.All(
1835 _base_trigger_list_flatten,
1836 [vol.All(_trigger_pre_validator, _base_trigger_validator)],
1839 _SCRIPT_DELAY_SCHEMA = vol.Schema(
1841 **SCRIPT_ACTION_BASE_SCHEMA,
1842 vol.Required(CONF_DELAY): positive_time_period_template,
1846 _SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema(
1848 **SCRIPT_ACTION_BASE_SCHEMA,
1849 vol.Required(CONF_WAIT_TEMPLATE): template,
1850 vol.Optional(CONF_TIMEOUT): positive_time_period_template,
1851 vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean,
1855 DEVICE_ACTION_BASE_SCHEMA = vol.Schema(
1857 **SCRIPT_ACTION_BASE_SCHEMA,
1858 vol.Required(CONF_DEVICE_ID): string,
1859 vol.Required(CONF_DOMAIN): str,
1860 vol.Remove(
"metadata"): dict,
1864 DEVICE_ACTION_SCHEMA = DEVICE_ACTION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
1866 _SCRIPT_SCENE_SCHEMA = vol.Schema(
1867 {**SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_SCENE):
entity_domain(
"scene")}
1870 _SCRIPT_REPEAT_SCHEMA = vol.Schema(
1872 **SCRIPT_ACTION_BASE_SCHEMA,
1873 vol.Required(CONF_REPEAT): vol.All(
1875 vol.Exclusive(CONF_COUNT,
"repeat"): vol.Any(vol.Coerce(int), template),
1876 vol.Exclusive(CONF_FOR_EACH,
"repeat"): vol.Any(
1877 dynamic_template, vol.All(list, template_complex)
1879 vol.Exclusive(CONF_WHILE,
"repeat"): vol.All(
1880 ensure_list, [CONDITION_SCHEMA]
1882 vol.Exclusive(CONF_UNTIL,
"repeat"): vol.All(
1883 ensure_list, [CONDITION_SCHEMA]
1885 vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA,
1892 _SCRIPT_CHOOSE_SCHEMA = vol.Schema(
1894 **SCRIPT_ACTION_BASE_SCHEMA,
1895 vol.Required(CONF_CHOOSE): vol.All(
1899 vol.Optional(CONF_ALIAS): string,
1900 vol.Required(CONF_CONDITIONS): vol.All(
1901 ensure_list, [CONDITION_SCHEMA]
1903 vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA,
1907 vol.Optional(CONF_DEFAULT): SCRIPT_SCHEMA,
1911 _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA = vol.Schema(
1913 **SCRIPT_ACTION_BASE_SCHEMA,
1914 vol.Required(CONF_WAIT_FOR_TRIGGER): TRIGGER_SCHEMA,
1915 vol.Optional(CONF_TIMEOUT): positive_time_period_template,
1916 vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean,
1920 _SCRIPT_IF_SCHEMA = vol.Schema(
1922 **SCRIPT_ACTION_BASE_SCHEMA,
1923 vol.Required(CONF_IF): vol.All(ensure_list, [CONDITION_SCHEMA]),
1924 vol.Required(CONF_THEN): SCRIPT_SCHEMA,
1925 vol.Optional(CONF_ELSE): SCRIPT_SCHEMA,
1929 _SCRIPT_SET_SCHEMA = vol.Schema(
1931 **SCRIPT_ACTION_BASE_SCHEMA,
1932 vol.Required(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA,
1936 _SCRIPT_SET_CONVERSATION_RESPONSE_SCHEMA = vol.Schema(
1938 **SCRIPT_ACTION_BASE_SCHEMA,
1940 CONF_SET_CONVERSATION_RESPONSE
1941 ): SCRIPT_CONVERSATION_RESPONSE_SCHEMA,
1945 _SCRIPT_STOP_SCHEMA = vol.Schema(
1947 **SCRIPT_ACTION_BASE_SCHEMA,
1948 vol.Required(CONF_STOP): vol.Any(
None, string),
1949 vol.Exclusive(CONF_ERROR,
"error_or_response"): boolean,
1951 CONF_RESPONSE_VARIABLE,
1952 "error_or_response",
1953 msg=
"not allowed to add a response to an error stop action",
1958 _SCRIPT_SEQUENCE_SCHEMA = vol.Schema(
1960 **SCRIPT_ACTION_BASE_SCHEMA,
1961 vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA,
1965 _parallel_sequence_action = vol.All(
1969 CONF_SEQUENCE: config,
1973 _SCRIPT_PARALLEL_SCHEMA = vol.Schema(
1975 **SCRIPT_ACTION_BASE_SCHEMA,
1976 vol.Required(CONF_PARALLEL): vol.All(
1977 ensure_list, [vol.Any(_SCRIPT_SEQUENCE_SCHEMA, _parallel_sequence_action)]
1983 SCRIPT_ACTION_ACTIVATE_SCENE =
"scene"
1984 SCRIPT_ACTION_CALL_SERVICE =
"call_service"
1985 SCRIPT_ACTION_CHECK_CONDITION =
"condition"
1986 SCRIPT_ACTION_CHOOSE =
"choose"
1987 SCRIPT_ACTION_DELAY =
"delay"
1988 SCRIPT_ACTION_DEVICE_AUTOMATION =
"device"
1989 SCRIPT_ACTION_FIRE_EVENT =
"event"
1990 SCRIPT_ACTION_IF =
"if"
1991 SCRIPT_ACTION_PARALLEL =
"parallel"
1992 SCRIPT_ACTION_REPEAT =
"repeat"
1993 SCRIPT_ACTION_SEQUENCE =
"sequence"
1994 SCRIPT_ACTION_SET_CONVERSATION_RESPONSE =
"set_conversation_response"
1995 SCRIPT_ACTION_STOP =
"stop"
1996 SCRIPT_ACTION_VARIABLES =
"variables"
1997 SCRIPT_ACTION_WAIT_FOR_TRIGGER =
"wait_for_trigger"
1998 SCRIPT_ACTION_WAIT_TEMPLATE =
"wait_template"
2002 CONF_DELAY: SCRIPT_ACTION_DELAY,
2003 CONF_WAIT_TEMPLATE: SCRIPT_ACTION_WAIT_TEMPLATE,
2004 CONF_CONDITION: SCRIPT_ACTION_CHECK_CONDITION,
2005 "and": SCRIPT_ACTION_CHECK_CONDITION,
2006 "or": SCRIPT_ACTION_CHECK_CONDITION,
2007 "not": SCRIPT_ACTION_CHECK_CONDITION,
2008 CONF_EVENT: SCRIPT_ACTION_FIRE_EVENT,
2009 CONF_DEVICE_ID: SCRIPT_ACTION_DEVICE_AUTOMATION,
2010 CONF_SCENE: SCRIPT_ACTION_ACTIVATE_SCENE,
2011 CONF_REPEAT: SCRIPT_ACTION_REPEAT,
2012 CONF_CHOOSE: SCRIPT_ACTION_CHOOSE,
2013 CONF_WAIT_FOR_TRIGGER: SCRIPT_ACTION_WAIT_FOR_TRIGGER,
2014 CONF_VARIABLES: SCRIPT_ACTION_VARIABLES,
2015 CONF_IF: SCRIPT_ACTION_IF,
2016 CONF_ACTION: SCRIPT_ACTION_CALL_SERVICE,
2017 CONF_SERVICE: SCRIPT_ACTION_CALL_SERVICE,
2018 CONF_SERVICE_TEMPLATE: SCRIPT_ACTION_CALL_SERVICE,
2019 CONF_STOP: SCRIPT_ACTION_STOP,
2020 CONF_PARALLEL: SCRIPT_ACTION_PARALLEL,
2021 CONF_SEQUENCE: SCRIPT_ACTION_SEQUENCE,
2022 CONF_SET_CONVERSATION_RESPONSE: SCRIPT_ACTION_SET_CONVERSATION_RESPONSE,
2025 ACTIONS_SET = set(ACTIONS_MAP)
2029 """Determine action type."""
2030 if not (actions := ACTIONS_SET.intersection(action)):
2031 raise ValueError(
"Unable to determine action")
2032 if len(actions) > 1:
2035 for action_key, _script_action
in ACTIONS_MAP.items():
2036 if action_key
in actions:
2037 return _script_action
2038 return ACTIONS_MAP[actions.pop()]
2041 ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = {
2042 SCRIPT_ACTION_ACTIVATE_SCENE: _SCRIPT_SCENE_SCHEMA,
2043 SCRIPT_ACTION_CALL_SERVICE: SERVICE_SCHEMA,
2044 SCRIPT_ACTION_CHECK_CONDITION: CONDITION_ACTION_SCHEMA,
2045 SCRIPT_ACTION_CHOOSE: _SCRIPT_CHOOSE_SCHEMA,
2046 SCRIPT_ACTION_DELAY: _SCRIPT_DELAY_SCHEMA,
2047 SCRIPT_ACTION_DEVICE_AUTOMATION: DEVICE_ACTION_SCHEMA,
2048 SCRIPT_ACTION_FIRE_EVENT: EVENT_SCHEMA,
2049 SCRIPT_ACTION_IF: _SCRIPT_IF_SCHEMA,
2050 SCRIPT_ACTION_PARALLEL: _SCRIPT_PARALLEL_SCHEMA,
2051 SCRIPT_ACTION_REPEAT: _SCRIPT_REPEAT_SCHEMA,
2052 SCRIPT_ACTION_SEQUENCE: _SCRIPT_SEQUENCE_SCHEMA,
2053 SCRIPT_ACTION_SET_CONVERSATION_RESPONSE: _SCRIPT_SET_CONVERSATION_RESPONSE_SCHEMA,
2054 SCRIPT_ACTION_STOP: _SCRIPT_STOP_SCHEMA,
2055 SCRIPT_ACTION_VARIABLES: _SCRIPT_SET_SCHEMA,
2056 SCRIPT_ACTION_WAIT_FOR_TRIGGER: _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA,
2057 SCRIPT_ACTION_WAIT_TEMPLATE: _SCRIPT_WAIT_TEMPLATE_SCHEMA,
2062 currencies.ACTIVE_CURRENCIES, msg=
"invalid ISO 4217 formatted currency"
2065 historic_currency = vol.In(
2066 currencies.HISTORIC_CURRENCIES, msg=
"invalid ISO 4217 formatted historic currency"
2069 country = vol.In(COUNTRIES, msg=
"invalid ISO 3166 formatted country")
2071 language = vol.In(LANGUAGES, msg=
"invalid RFC 5646 formatted language")
2075 hass: HomeAssistant, validator: Callable[[Any], Any], value: Any
2077 """Async friendly schema validation.
2079 If a validator decorated with @not_async_friendly is called, validation will be
2080 deferred to an executor. If not, validation will happen in the event loop.
2082 _validating_async.set(
True)
2084 return validator(value)
2085 except MustValidateInExecutor:
2086 return await hass.async_add_executor_job(
2087 _validate_in_executor, hass, validator, value
2090 _validating_async.set(
False)
2094 hass: HomeAssistant, validator: Callable[[Any], Any], value: Any
2098 return validator(value)
list __call__(self, list selected)
None __init__(self, dict|list options)
vol.Schema default_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
None async_create_issue(HomeAssistant hass, str entry_id)
dict[str, Any] validate(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
bool valid_entity_id(str entity_id)
HomeAssistant|None async_get_hass_or_none()
HomeAssistant async_get_hass()
tuple[str, str] split_entity_id(str entity_id)
VolSchemaType make_entity_service_schema(dict|None schema, *int extra=vol.PREVENT_EXTRA)
str domain_key(Any config_key)
Any _custom_serializer(Any schema, *bool allow_section)
str url(Any value, frozenset[UrlProtocolSchema] _schema_list=EXTERNAL_URL_PROTOCOL_SCHEMA_LIST)
Callable[[dict], dict] has_at_least_one_key(*Any keys)
list[Any] ensure_list(None value)
str whitespace(Any value)
Callable[[dict], dict] _no_yaml_config_schema(str domain, str issue_base, str translation_key, dict[str, str] translation_placeholders)
str determine_script_action(dict[str, Any] action)
VolSchemaType _make_entity_service_schema(dict schema, int extra)
str x10_address(str value)
Callable[[dict], dict] _deprecated_or_removed(str key, str|None replacement_key, Any|None default, bool raise_if_present, bool option_removed)
Callable[[dict], dict] platform_only_config_schema(str domain)
CONDITION_SHORTHAND_SCHEMA
Callable[[dict], dict] empty_config_schema(str domain)
datetime_sys datetime(Any value)
Callable[[dict], dict] removed(str key, Any|None default=None, bool|None raise_if_present=True)
vol.All enum(type[Enum] enumClass)
Callable[[dict], dict] has_at_most_one_key(*Any keys)
Any _backward_compat_service_schema(Any|None value)
template_helper.Template dynamic_template(Any|None value)
Callable[[Any], dict[Hashable, Any]] key_value_schemas(str key, dict[Hashable, VolSchemaType|Callable[[Any], dict[str, Any]]] value_schemas, VolSchemaType|None default_schema=None, str|None default_description=None)
str url_no_path(Any value)
timedelta time_period_str(str value)
Any template_complex(Any value)
UnitOfTemperature temperature_unit(Any value)
STATE_CONDITION_ATTRIBUTE_SCHEMA
list ensure_list_csv(Any value)
dict[str, Any] STATE_CONDITION_SCHEMA(Any value)
list[str] _entity_ids(str|list value, bool allow_uuid)
Callable[[Any], str] entity_domain(str|list[str] domain)
Any _positive_time_period_template_complex(Any value)
bool is_entity_service_schema(VolSchemaType validator)
Callable[[dict], dict] config_entry_only_config_schema(str domain)
list[str] entity_ids_or_uuids(str|list value)
_base_trigger_validator_schema
Callable[[Any], str] matches_regex(str regex)
str entity_id_or_uuid(Any value)
object socket_timeout(Any|None value)
Callable[[dict], dict] deprecated(str key, str|None replacement_key=None, Any|None default=None, bool|None raise_if_present=False)
Any async_validate(HomeAssistant hass, Callable[[Any], Any] validator, Any value)
str string_with_no_html(Any value)
Any custom_serializer(Any schema)
list[Any] _base_trigger_list_flatten(list[Any] triggers)
str configuration_url(Any value)
list[str] entity_ids(str|list value)
Any _base_trigger_validator(Any value)
re.Pattern[Any] is_regex(Any value)
HomeAssistant|None _async_get_hass_or_none()
Any expand_condition_shorthand(Any|None value)
Callable[[str|list], list[str]] entities_domain(str|list[str] domain)
timedelta positive_timedelta(timedelta value)
STATE_CONDITION_STATE_SCHEMA
dict script_action(Any value)
template_helper.Template template(Any|None value)
timedelta time_period_seconds(float|str value)
Any _trigger_pre_validator(Any|None value)
str fake_uuid4_hex(Any value)
Any _validate_in_executor(HomeAssistant hass, Callable[[Any], Any] validator, Any value)
Callable schema_with_slug_keys(dict|Callable value_schema, *Callable[[Any], str] slug_validator=slug)
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
logging.Logger get_integration_logger(str fallback_name)
None raise_if_invalid_path(str path)