Black
This commit is contained in:
parent
da05dfe708
commit
4de97abc3a
2676 changed files with 163166 additions and 140084 deletions
|
@ -3,8 +3,12 @@ import inspect
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import (timedelta, datetime as datetime_sys,
|
||||
time as time_sys, date as date_sys)
|
||||
from datetime import (
|
||||
timedelta,
|
||||
datetime as datetime_sys,
|
||||
time as time_sys,
|
||||
date as date_sys,
|
||||
)
|
||||
from socket import _GLOBAL_DEFAULT_TIMEOUT
|
||||
from numbers import Number
|
||||
from typing import Any, Union, TypeVar, Callable, Sequence, Dict, Optional
|
||||
|
@ -16,12 +20,28 @@ from pkg_resources import parse_version
|
|||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
CONF_ABOVE, CONF_ALIAS, CONF_BELOW, CONF_CONDITION, CONF_ENTITY_ID,
|
||||
CONF_ENTITY_NAMESPACE, CONF_PLATFORM, CONF_SCAN_INTERVAL,
|
||||
CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, CONF_VALUE_TEMPLATE,
|
||||
CONF_TIMEOUT, ENTITY_MATCH_ALL, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, WEEKDAYS, __version__, ATTR_AREA_ID,
|
||||
ATTR_ENTITY_ID)
|
||||
CONF_ABOVE,
|
||||
CONF_ALIAS,
|
||||
CONF_BELOW,
|
||||
CONF_CONDITION,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ENTITY_NAMESPACE,
|
||||
CONF_PLATFORM,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_UNIT_SYSTEM_IMPERIAL,
|
||||
CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
CONF_TIMEOUT,
|
||||
ENTITY_MATCH_ALL,
|
||||
SUN_EVENT_SUNRISE,
|
||||
SUN_EVENT_SUNSET,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
WEEKDAYS,
|
||||
__version__,
|
||||
ATTR_AREA_ID,
|
||||
ATTR_ENTITY_ID,
|
||||
)
|
||||
from homeassistant.core import valid_entity_id, split_entity_id
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.logging import KeywordStyleAdapter
|
||||
|
@ -39,46 +59,48 @@ TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'"
|
|||
byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255))
|
||||
small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
|
||||
positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
|
||||
latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90),
|
||||
msg='invalid latitude')
|
||||
longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180),
|
||||
msg='invalid longitude')
|
||||
latitude = vol.All(
|
||||
vol.Coerce(float), vol.Range(min=-90, max=90), msg="invalid latitude"
|
||||
)
|
||||
longitude = vol.All(
|
||||
vol.Coerce(float), vol.Range(min=-180, max=180), msg="invalid longitude"
|
||||
)
|
||||
gps = vol.ExactSequence([latitude, longitude])
|
||||
sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE))
|
||||
port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||
|
||||
# typing typevar
|
||||
T = TypeVar('T')
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# Adapted from:
|
||||
# https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666
|
||||
def has_at_least_one_key(*keys: str) -> Callable:
|
||||
"""Validate that at least one key exists."""
|
||||
|
||||
def validate(obj: Dict) -> Dict:
|
||||
"""Test keys exist in dict."""
|
||||
if not isinstance(obj, dict):
|
||||
raise vol.Invalid('expected dictionary')
|
||||
raise vol.Invalid("expected dictionary")
|
||||
|
||||
for k in obj.keys():
|
||||
if k in keys:
|
||||
return obj
|
||||
raise vol.Invalid('must contain one of {}.'.format(', '.join(keys)))
|
||||
raise vol.Invalid("must contain one of {}.".format(", ".join(keys)))
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
def has_at_most_one_key(*keys: str) -> Callable:
|
||||
"""Validate that zero keys exist or one key exists."""
|
||||
|
||||
def validate(obj: Dict) -> Dict:
|
||||
"""Test zero keys exist or one key exists in dict."""
|
||||
if not isinstance(obj, dict):
|
||||
raise vol.Invalid('expected dictionary')
|
||||
raise vol.Invalid("expected dictionary")
|
||||
|
||||
if len(set(keys) & set(obj)) > 1:
|
||||
raise vol.Invalid(
|
||||
'must contain at most one of {}.'.format(', '.join(keys))
|
||||
)
|
||||
raise vol.Invalid("must contain at most one of {}.".format(", ".join(keys)))
|
||||
return obj
|
||||
|
||||
return validate
|
||||
|
@ -90,14 +112,14 @@ def boolean(value: Any) -> bool:
|
|||
return value
|
||||
if isinstance(value, str):
|
||||
value = value.lower().strip()
|
||||
if value in ('1', 'true', 'yes', 'on', 'enable'):
|
||||
if value in ("1", "true", "yes", "on", "enable"):
|
||||
return True
|
||||
if value in ('0', 'false', 'no', 'off', 'disable'):
|
||||
if value in ("0", "false", "no", "off", "disable"):
|
||||
return False
|
||||
elif isinstance(value, Number):
|
||||
# type ignore: https://github.com/python/mypy/issues/3186
|
||||
return value != 0 # type: ignore
|
||||
raise vol.Invalid('invalid boolean value {}'.format(value))
|
||||
raise vol.Invalid("invalid boolean value {}".format(value))
|
||||
|
||||
|
||||
def isdevice(value):
|
||||
|
@ -106,7 +128,7 @@ def isdevice(value):
|
|||
os.stat(value)
|
||||
return str(value)
|
||||
except OSError:
|
||||
raise vol.Invalid('No device at {} found'.format(value))
|
||||
raise vol.Invalid("No device at {} found".format(value))
|
||||
|
||||
|
||||
def matches_regex(regex):
|
||||
|
@ -116,13 +138,17 @@ def matches_regex(regex):
|
|||
def validator(value: Any) -> str:
|
||||
"""Validate that value matches the given regex."""
|
||||
if not isinstance(value, str):
|
||||
raise vol.Invalid('not a string value: {}'.format(value))
|
||||
raise vol.Invalid("not a string value: {}".format(value))
|
||||
|
||||
if not regex.match(value):
|
||||
raise vol.Invalid('value {} does not match regular expression {}'
|
||||
.format(value, regex.pattern))
|
||||
raise vol.Invalid(
|
||||
"value {} does not match regular expression {}".format(
|
||||
value, regex.pattern
|
||||
)
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
|
@ -132,36 +158,36 @@ def is_regex(value):
|
|||
r = re.compile(value)
|
||||
return r
|
||||
except TypeError:
|
||||
raise vol.Invalid("value {} is of the wrong type for a regular "
|
||||
"expression".format(value))
|
||||
raise vol.Invalid(
|
||||
"value {} is of the wrong type for a regular " "expression".format(value)
|
||||
)
|
||||
except re.error:
|
||||
raise vol.Invalid("value {} is not a valid regular expression".format(
|
||||
value))
|
||||
raise vol.Invalid("value {} is not a valid regular expression".format(value))
|
||||
|
||||
|
||||
def isfile(value: Any) -> str:
|
||||
"""Validate that the value is an existing file."""
|
||||
if value is None:
|
||||
raise vol.Invalid('None is not file')
|
||||
raise vol.Invalid("None is not file")
|
||||
file_in = os.path.expanduser(str(value))
|
||||
|
||||
if not os.path.isfile(file_in):
|
||||
raise vol.Invalid('not a file')
|
||||
raise vol.Invalid("not a file")
|
||||
if not os.access(file_in, os.R_OK):
|
||||
raise vol.Invalid('file not readable')
|
||||
raise vol.Invalid("file not readable")
|
||||
return file_in
|
||||
|
||||
|
||||
def isdir(value: Any) -> str:
|
||||
"""Validate that the value is an existing dir."""
|
||||
if value is None:
|
||||
raise vol.Invalid('not a directory')
|
||||
raise vol.Invalid("not a directory")
|
||||
dir_in = os.path.expanduser(str(value))
|
||||
|
||||
if not os.path.isdir(dir_in):
|
||||
raise vol.Invalid('not a directory')
|
||||
raise vol.Invalid("not a directory")
|
||||
if not os.access(dir_in, os.R_OK):
|
||||
raise vol.Invalid('directory not readable')
|
||||
raise vol.Invalid("directory not readable")
|
||||
return dir_in
|
||||
|
||||
|
||||
|
@ -178,45 +204,48 @@ def entity_id(value: Any) -> str:
|
|||
if valid_entity_id(value):
|
||||
return value
|
||||
|
||||
raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value))
|
||||
raise vol.Invalid("Entity ID {} is an invalid entity id".format(value))
|
||||
|
||||
|
||||
def entity_ids(value: Union[str, Sequence]) -> Sequence[str]:
|
||||
"""Validate Entity IDs."""
|
||||
if value is None:
|
||||
raise vol.Invalid('Entity IDs can not be None')
|
||||
raise vol.Invalid("Entity IDs can not be None")
|
||||
if isinstance(value, str):
|
||||
value = [ent_id.strip() for ent_id in value.split(',')]
|
||||
value = [ent_id.strip() for ent_id in value.split(",")]
|
||||
|
||||
return [entity_id(ent_id) for ent_id in value]
|
||||
|
||||
|
||||
comp_entity_ids = vol.Any(
|
||||
vol.All(vol.Lower, ENTITY_MATCH_ALL),
|
||||
entity_ids
|
||||
)
|
||||
comp_entity_ids = vol.Any(vol.All(vol.Lower, ENTITY_MATCH_ALL), entity_ids)
|
||||
|
||||
|
||||
def entity_domain(domain: str):
|
||||
"""Validate that entity belong to domain."""
|
||||
|
||||
def validate(value: Any) -> str:
|
||||
"""Test if entity domain is domain."""
|
||||
ent_domain = entities_domain(domain)
|
||||
return ent_domain(value)[0]
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
def entities_domain(domain: str):
|
||||
"""Validate that entities belong to domain."""
|
||||
|
||||
def validate(values: Union[str, Sequence]) -> Sequence[str]:
|
||||
"""Test if entity domain is domain."""
|
||||
values = entity_ids(values)
|
||||
for ent_id in values:
|
||||
if split_entity_id(ent_id)[0] != domain:
|
||||
raise vol.Invalid(
|
||||
"Entity ID '{}' does not belong to domain '{}'"
|
||||
.format(ent_id, domain))
|
||||
"Entity ID '{}' does not belong to domain '{}'".format(
|
||||
ent_id, domain
|
||||
)
|
||||
)
|
||||
return values
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
|
@ -229,23 +258,26 @@ def icon(value):
|
|||
"""Validate icon."""
|
||||
value = str(value)
|
||||
|
||||
if ':' in value:
|
||||
if ":" in value:
|
||||
return value
|
||||
|
||||
raise vol.Invalid('Icons should be specifed on the form "prefix:name"')
|
||||
|
||||
|
||||
time_period_dict = vol.All(
|
||||
dict, vol.Schema({
|
||||
'days': vol.Coerce(int),
|
||||
'hours': vol.Coerce(int),
|
||||
'minutes': vol.Coerce(int),
|
||||
'seconds': vol.Coerce(int),
|
||||
'milliseconds': vol.Coerce(int),
|
||||
}),
|
||||
has_at_least_one_key('days', 'hours', 'minutes',
|
||||
'seconds', 'milliseconds'),
|
||||
lambda value: timedelta(**value))
|
||||
dict,
|
||||
vol.Schema(
|
||||
{
|
||||
"days": vol.Coerce(int),
|
||||
"hours": vol.Coerce(int),
|
||||
"minutes": vol.Coerce(int),
|
||||
"seconds": vol.Coerce(int),
|
||||
"milliseconds": vol.Coerce(int),
|
||||
}
|
||||
),
|
||||
has_at_least_one_key("days", "hours", "minutes", "seconds", "milliseconds"),
|
||||
lambda value: timedelta(**value),
|
||||
)
|
||||
|
||||
|
||||
def time(value) -> time_sys:
|
||||
|
@ -256,10 +288,10 @@ def time(value) -> time_sys:
|
|||
try:
|
||||
time_val = dt_util.parse_time(value)
|
||||
except TypeError:
|
||||
raise vol.Invalid('Not a parseable type')
|
||||
raise vol.Invalid("Not a parseable type")
|
||||
|
||||
if time_val is None:
|
||||
raise vol.Invalid('Invalid time specified: {}'.format(value))
|
||||
raise vol.Invalid("Invalid time specified: {}".format(value))
|
||||
|
||||
return time_val
|
||||
|
||||
|
@ -272,7 +304,7 @@ def date(value) -> date_sys:
|
|||
try:
|
||||
date_val = dt_util.parse_date(value)
|
||||
except TypeError:
|
||||
raise vol.Invalid('Not a parseable type')
|
||||
raise vol.Invalid("Not a parseable type")
|
||||
|
||||
if date_val is None:
|
||||
raise vol.Invalid("Could not parse date")
|
||||
|
@ -283,19 +315,19 @@ def date(value) -> date_sys:
|
|||
def time_period_str(value: str) -> timedelta:
|
||||
"""Validate and transform time offset."""
|
||||
if isinstance(value, int):
|
||||
raise vol.Invalid('Make sure you wrap time values in quotes')
|
||||
raise vol.Invalid("Make sure you wrap time values in quotes")
|
||||
if not isinstance(value, str):
|
||||
raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
|
||||
|
||||
negative_offset = False
|
||||
if value.startswith('-'):
|
||||
if value.startswith("-"):
|
||||
negative_offset = True
|
||||
value = value[1:]
|
||||
elif value.startswith('+'):
|
||||
elif value.startswith("+"):
|
||||
value = value[1:]
|
||||
|
||||
try:
|
||||
parsed = [int(x) for x in value.split(':')]
|
||||
parsed = [int(x) for x in value.split(":")]
|
||||
except ValueError:
|
||||
raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
|
||||
|
||||
|
@ -320,11 +352,10 @@ def time_period_seconds(value: Union[int, str]) -> timedelta:
|
|||
try:
|
||||
return timedelta(seconds=int(value))
|
||||
except (ValueError, TypeError):
|
||||
raise vol.Invalid('Expected seconds, got {}'.format(value))
|
||||
raise vol.Invalid("Expected seconds, got {}".format(value))
|
||||
|
||||
|
||||
time_period = vol.Any(time_period_str, time_period_seconds, timedelta,
|
||||
time_period_dict)
|
||||
time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict)
|
||||
|
||||
|
||||
def match_all(value):
|
||||
|
@ -335,7 +366,7 @@ def match_all(value):
|
|||
def positive_timedelta(value: timedelta) -> timedelta:
|
||||
"""Validate timedelta is positive."""
|
||||
if value < timedelta(0):
|
||||
raise vol.Invalid('Time period should be positive')
|
||||
raise vol.Invalid("Time period should be positive")
|
||||
return value
|
||||
|
||||
|
||||
|
@ -349,8 +380,7 @@ def service(value):
|
|||
# Services use same format as entities so we can use same helper.
|
||||
if valid_entity_id(value):
|
||||
return value
|
||||
raise vol.Invalid('Service {} does not match format <domain>.<name>'
|
||||
.format(value))
|
||||
raise vol.Invalid("Service {} does not match format <domain>.<name>".format(value))
|
||||
|
||||
|
||||
def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable:
|
||||
|
@ -364,42 +394,43 @@ def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable:
|
|||
def verify(value: Dict) -> Dict:
|
||||
"""Validate all keys are slugs and then the value_schema."""
|
||||
if not isinstance(value, dict):
|
||||
raise vol.Invalid('expected dictionary')
|
||||
raise vol.Invalid("expected dictionary")
|
||||
|
||||
for key in value.keys():
|
||||
slug(key)
|
||||
|
||||
return schema(value)
|
||||
|
||||
return verify
|
||||
|
||||
|
||||
def slug(value: Any) -> str:
|
||||
"""Validate value is a valid slug."""
|
||||
if value is None:
|
||||
raise vol.Invalid('Slug should not be None')
|
||||
raise vol.Invalid("Slug should not be None")
|
||||
value = str(value)
|
||||
slg = util_slugify(value)
|
||||
if value == slg:
|
||||
return value
|
||||
raise vol.Invalid('invalid slug {} (try {})'.format(value, slg))
|
||||
raise vol.Invalid("invalid slug {} (try {})".format(value, slg))
|
||||
|
||||
|
||||
def slugify(value: Any) -> str:
|
||||
"""Coerce a value to a slug."""
|
||||
if value is None:
|
||||
raise vol.Invalid('Slug should not be None')
|
||||
raise vol.Invalid("Slug should not be None")
|
||||
slg = util_slugify(str(value))
|
||||
if slg:
|
||||
return slg
|
||||
raise vol.Invalid('Unable to slugify {}'.format(value))
|
||||
raise vol.Invalid("Unable to slugify {}".format(value))
|
||||
|
||||
|
||||
def string(value: Any) -> str:
|
||||
"""Coerce value to string, except for None."""
|
||||
if value is None:
|
||||
raise vol.Invalid('string value is None')
|
||||
raise vol.Invalid("string value is None")
|
||||
if isinstance(value, (list, dict)):
|
||||
raise vol.Invalid('value should be a string')
|
||||
raise vol.Invalid("value should be a string")
|
||||
|
||||
return str(value)
|
||||
|
||||
|
@ -407,15 +438,16 @@ def string(value: Any) -> str:
|
|||
def temperature_unit(value) -> str:
|
||||
"""Validate and transform temperature unit."""
|
||||
value = str(value).upper()
|
||||
if value == 'C':
|
||||
if value == "C":
|
||||
return TEMP_CELSIUS
|
||||
if value == 'F':
|
||||
if value == "F":
|
||||
return TEMP_FAHRENHEIT
|
||||
raise vol.Invalid('invalid temperature unit (expected C or F)')
|
||||
raise vol.Invalid("invalid temperature unit (expected C or F)")
|
||||
|
||||
|
||||
unit_system = vol.All(vol.Lower, vol.Any(CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_UNIT_SYSTEM_IMPERIAL))
|
||||
unit_system = vol.All(
|
||||
vol.Lower, vol.Any(CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL)
|
||||
)
|
||||
|
||||
|
||||
def template(value):
|
||||
|
@ -423,9 +455,9 @@ def template(value):
|
|||
from homeassistant.helpers import template as template_helper
|
||||
|
||||
if value is None:
|
||||
raise vol.Invalid('template value is None')
|
||||
raise vol.Invalid("template value is None")
|
||||
if isinstance(value, (list, dict, template_helper.Template)):
|
||||
raise vol.Invalid('template value should be a string')
|
||||
raise vol.Invalid("template value should be a string")
|
||||
|
||||
value = template_helper.Template(str(value))
|
||||
|
||||
|
@ -433,7 +465,7 @@ def template(value):
|
|||
value.ensure_valid()
|
||||
return value
|
||||
except TemplateError as ex:
|
||||
raise vol.Invalid('invalid template ({})'.format(ex))
|
||||
raise vol.Invalid("invalid template ({})".format(ex))
|
||||
|
||||
|
||||
def template_complex(value):
|
||||
|
@ -463,7 +495,7 @@ def datetime(value):
|
|||
date_val = None
|
||||
|
||||
if date_val is None:
|
||||
raise vol.Invalid('Invalid datetime specified: {}'.format(value))
|
||||
raise vol.Invalid("Invalid datetime specified: {}".format(value))
|
||||
|
||||
return date_val
|
||||
|
||||
|
@ -473,8 +505,9 @@ def time_zone(value):
|
|||
if dt_util.get_time_zone(value) is not None:
|
||||
return value
|
||||
raise vol.Invalid(
|
||||
'Invalid time zone passed in. Valid options can be found here: '
|
||||
'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones')
|
||||
"Invalid time zone passed in. Valid options can be found here: "
|
||||
"http://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
|
||||
)
|
||||
|
||||
|
||||
weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)])
|
||||
|
@ -491,10 +524,9 @@ def socket_timeout(value):
|
|||
float_value = float(value)
|
||||
if float_value > 0.0:
|
||||
return float_value
|
||||
raise vol.Invalid('Invalid socket timeout value.'
|
||||
' float > 0.0 required.')
|
||||
raise vol.Invalid("Invalid socket timeout value." " float > 0.0 required.")
|
||||
except Exception as _:
|
||||
raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_))
|
||||
raise vol.Invalid("Invalid socket timeout: {err}".format(err=_))
|
||||
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
|
@ -502,17 +534,17 @@ def url(value: Any) -> str:
|
|||
"""Validate an URL."""
|
||||
url_in = str(value)
|
||||
|
||||
if urlparse(url_in).scheme in ['http', 'https']:
|
||||
if urlparse(url_in).scheme in ["http", "https"]:
|
||||
return vol.Schema(vol.Url())(url_in)
|
||||
|
||||
raise vol.Invalid('invalid url')
|
||||
raise vol.Invalid("invalid url")
|
||||
|
||||
|
||||
def x10_address(value):
|
||||
"""Validate an x10 address."""
|
||||
regex = re.compile(r'([A-Pa-p]{1})(?:[2-9]|1[0-6]?)$')
|
||||
regex = re.compile(r"([A-Pa-p]{1})(?:[2-9]|1[0-6]?)$")
|
||||
if not regex.match(value):
|
||||
raise vol.Invalid('Invalid X10 Address')
|
||||
raise vol.Invalid("Invalid X10 Address")
|
||||
return str(value).lower()
|
||||
|
||||
|
||||
|
@ -521,11 +553,11 @@ def uuid4_hex(value):
|
|||
try:
|
||||
result = UUID(value, version=4)
|
||||
except (ValueError, AttributeError, TypeError) as error:
|
||||
raise vol.Invalid('Invalid Version4 UUID', error_message=str(error))
|
||||
raise vol.Invalid("Invalid Version4 UUID", error_message=str(error))
|
||||
|
||||
if result.hex != value.lower():
|
||||
# UUID() will create a uuid4 if input is invalid
|
||||
raise vol.Invalid('Invalid Version4 UUID')
|
||||
raise vol.Invalid("Invalid Version4 UUID")
|
||||
|
||||
return result.hex
|
||||
|
||||
|
@ -533,14 +565,16 @@ def uuid4_hex(value):
|
|||
def ensure_list_csv(value: Any) -> Sequence:
|
||||
"""Ensure that input is a list or make one from comma-separated string."""
|
||||
if isinstance(value, str):
|
||||
return [member.strip() for member in value.split(',')]
|
||||
return [member.strip() for member in value.split(",")]
|
||||
return ensure_list(value)
|
||||
|
||||
|
||||
def deprecated(key: str,
|
||||
replacement_key: Optional[str] = None,
|
||||
invalidation_version: Optional[str] = None,
|
||||
default: Optional[Any] = None):
|
||||
def deprecated(
|
||||
key: str,
|
||||
replacement_key: Optional[str] = None,
|
||||
invalidation_version: Optional[str] = None,
|
||||
default: Optional[Any] = None,
|
||||
):
|
||||
"""
|
||||
Log key as deprecated and provide a replacement (if exists).
|
||||
|
||||
|
@ -564,21 +598,29 @@ def deprecated(key: str,
|
|||
module_name = __name__ # type: ignore
|
||||
|
||||
if replacement_key and invalidation_version:
|
||||
warning = ("The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please replace it with '{replacement_key}'."
|
||||
" This option will become invalid in version"
|
||||
" {invalidation_version}")
|
||||
warning = (
|
||||
"The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please replace it with '{replacement_key}'."
|
||||
" This option will become invalid in version"
|
||||
" {invalidation_version}"
|
||||
)
|
||||
elif replacement_key:
|
||||
warning = ("The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please replace it with '{replacement_key}'")
|
||||
warning = (
|
||||
"The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please replace it with '{replacement_key}'"
|
||||
)
|
||||
elif invalidation_version:
|
||||
warning = ("The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please remove it from your configuration."
|
||||
" This option will become invalid in version"
|
||||
" {invalidation_version}")
|
||||
warning = (
|
||||
"The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please remove it from your configuration."
|
||||
" This option will become invalid in version"
|
||||
" {invalidation_version}"
|
||||
)
|
||||
else:
|
||||
warning = ("The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please remove it from your configuration")
|
||||
warning = (
|
||||
"The '{key}' option (with value '{value}') is"
|
||||
" deprecated, please remove it from your configuration"
|
||||
)
|
||||
|
||||
def check_for_invalid_version(value: Optional[Any]):
|
||||
"""Raise error if current version has reached invalidation."""
|
||||
|
@ -591,7 +633,7 @@ def deprecated(key: str,
|
|||
key=key,
|
||||
value=value,
|
||||
replacement_key=replacement_key,
|
||||
invalidation_version=invalidation_version
|
||||
invalidation_version=invalidation_version,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -605,7 +647,7 @@ def deprecated(key: str,
|
|||
key=key,
|
||||
value=value,
|
||||
replacement_key=replacement_key,
|
||||
invalidation_version=invalidation_version
|
||||
invalidation_version=invalidation_version,
|
||||
)
|
||||
if replacement_key:
|
||||
config.pop(key)
|
||||
|
@ -615,8 +657,8 @@ def deprecated(key: str,
|
|||
if replacement_key:
|
||||
keys.append(replacement_key)
|
||||
if value is not None and (
|
||||
replacement_key not in config or
|
||||
default == config.get(replacement_key)):
|
||||
replacement_key not in config or default == config.get(replacement_key)
|
||||
):
|
||||
config[replacement_key] = value
|
||||
|
||||
return has_at_most_one_key(*keys)(config)
|
||||
|
@ -626,116 +668,160 @@ def deprecated(key: str,
|
|||
|
||||
# Validator helpers
|
||||
|
||||
|
||||
def key_dependency(key, dependency):
|
||||
"""Validate that all dependencies exist for key."""
|
||||
|
||||
def validator(value):
|
||||
"""Test dependencies."""
|
||||
if not isinstance(value, dict):
|
||||
raise vol.Invalid('key dependencies require a dict')
|
||||
raise vol.Invalid("key dependencies require a dict")
|
||||
if key in value and dependency not in value:
|
||||
raise vol.Invalid('dependency violation - key "{}" requires '
|
||||
'key "{}" to exist'.format(key, dependency))
|
||||
raise vol.Invalid(
|
||||
'dependency violation - key "{}" requires '
|
||||
'key "{}" to exist'.format(key, dependency)
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
# Schemas
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): string,
|
||||
vol.Optional(CONF_ENTITY_NAMESPACE): string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL): time_period
|
||||
})
|
||||
PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): string,
|
||||
vol.Optional(CONF_ENTITY_NAMESPACE): string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL): time_period,
|
||||
}
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
ENTITY_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids,
|
||||
vol.Optional(ATTR_AREA_ID): vol.All(ensure_list, [str]),
|
||||
})
|
||||
ENTITY_SERVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids,
|
||||
vol.Optional(ATTR_AREA_ID): vol.All(ensure_list, [str]),
|
||||
}
|
||||
)
|
||||
|
||||
EVENT_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required('event'): string,
|
||||
vol.Optional('event_data'): dict,
|
||||
vol.Optional('event_data_template'): {match_all: template_complex}
|
||||
})
|
||||
EVENT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required("event"): string,
|
||||
vol.Optional("event_data"): dict,
|
||||
vol.Optional("event_data_template"): {match_all: template_complex},
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Exclusive('service', 'service name'): service,
|
||||
vol.Exclusive('service_template', 'service name'): template,
|
||||
vol.Optional('data'): dict,
|
||||
vol.Optional('data_template'): {match_all: template_complex},
|
||||
vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
|
||||
}), has_at_least_one_key('service', 'service_template'))
|
||||
SERVICE_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Exclusive("service", "service name"): service,
|
||||
vol.Exclusive("service_template", "service name"): template,
|
||||
vol.Optional("data"): dict,
|
||||
vol.Optional("data_template"): {match_all: template_complex},
|
||||
vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
|
||||
}
|
||||
),
|
||||
has_at_least_one_key("service", "service_template"),
|
||||
)
|
||||
|
||||
NUMERIC_STATE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'numeric_state',
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
CONF_BELOW: vol.Coerce(float),
|
||||
CONF_ABOVE: vol.Coerce(float),
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): template,
|
||||
}), has_at_least_one_key(CONF_BELOW, CONF_ABOVE))
|
||||
NUMERIC_STATE_CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "numeric_state",
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
CONF_BELOW: vol.Coerce(float),
|
||||
CONF_ABOVE: vol.Coerce(float),
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): template,
|
||||
}
|
||||
),
|
||||
has_at_least_one_key(CONF_BELOW, CONF_ABOVE),
|
||||
)
|
||||
|
||||
STATE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'state',
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
vol.Required('state'): str,
|
||||
vol.Optional('for'): vol.All(time_period, positive_timedelta),
|
||||
# To support use_trigger_value in automation
|
||||
# Deprecated 2016/04/25
|
||||
vol.Optional('from'): str,
|
||||
}), key_dependency('for', 'state'))
|
||||
STATE_CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "state",
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
vol.Required("state"): str,
|
||||
vol.Optional("for"): vol.All(time_period, positive_timedelta),
|
||||
# To support use_trigger_value in automation
|
||||
# Deprecated 2016/04/25
|
||||
vol.Optional("from"): str,
|
||||
}
|
||||
),
|
||||
key_dependency("for", "state"),
|
||||
)
|
||||
|
||||
SUN_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'sun',
|
||||
vol.Optional('before'): sun_event,
|
||||
vol.Optional('before_offset'): time_period,
|
||||
vol.Optional('after'): vol.All(vol.Lower, vol.Any(
|
||||
SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)),
|
||||
vol.Optional('after_offset'): time_period,
|
||||
}), has_at_least_one_key('before', 'after'))
|
||||
SUN_CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "sun",
|
||||
vol.Optional("before"): sun_event,
|
||||
vol.Optional("before_offset"): time_period,
|
||||
vol.Optional("after"): vol.All(
|
||||
vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)
|
||||
),
|
||||
vol.Optional("after_offset"): time_period,
|
||||
}
|
||||
),
|
||||
has_at_least_one_key("before", "after"),
|
||||
)
|
||||
|
||||
TEMPLATE_CONDITION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'template',
|
||||
vol.Required(CONF_VALUE_TEMPLATE): template,
|
||||
})
|
||||
TEMPLATE_CONDITION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "template",
|
||||
vol.Required(CONF_VALUE_TEMPLATE): template,
|
||||
}
|
||||
)
|
||||
|
||||
TIME_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'time',
|
||||
'before': time,
|
||||
'after': time,
|
||||
'weekday': weekdays,
|
||||
}), has_at_least_one_key('before', 'after', 'weekday'))
|
||||
TIME_CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "time",
|
||||
"before": time,
|
||||
"after": time,
|
||||
"weekday": weekdays,
|
||||
}
|
||||
),
|
||||
has_at_least_one_key("before", "after", "weekday"),
|
||||
)
|
||||
|
||||
ZONE_CONDITION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'zone',
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
'zone': entity_id,
|
||||
# To support use_trigger_value in automation
|
||||
# Deprecated 2016/04/25
|
||||
vol.Optional('event'): vol.Any('enter', 'leave'),
|
||||
})
|
||||
ZONE_CONDITION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "zone",
|
||||
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||
"zone": entity_id,
|
||||
# To support use_trigger_value in automation
|
||||
# Deprecated 2016/04/25
|
||||
vol.Optional("event"): vol.Any("enter", "leave"),
|
||||
}
|
||||
)
|
||||
|
||||
AND_CONDITION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'and',
|
||||
vol.Required('conditions'): vol.All(
|
||||
ensure_list,
|
||||
# pylint: disable=unnecessary-lambda
|
||||
[lambda value: CONDITION_SCHEMA(value)],
|
||||
)
|
||||
})
|
||||
AND_CONDITION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "and",
|
||||
vol.Required("conditions"): vol.All(
|
||||
ensure_list,
|
||||
# pylint: disable=unnecessary-lambda
|
||||
[lambda value: CONDITION_SCHEMA(value)],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
OR_CONDITION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CONDITION): 'or',
|
||||
vol.Required('conditions'): vol.All(
|
||||
ensure_list,
|
||||
# pylint: disable=unnecessary-lambda
|
||||
[lambda value: CONDITION_SCHEMA(value)],
|
||||
)
|
||||
})
|
||||
OR_CONDITION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONDITION): "or",
|
||||
vol.Required("conditions"): vol.All(
|
||||
ensure_list,
|
||||
# pylint: disable=unnecessary-lambda
|
||||
[lambda value: CONDITION_SCHEMA(value)],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONDITION_SCHEMA = vol.Any(
|
||||
NUMERIC_STATE_CONDITION_SCHEMA,
|
||||
|
@ -748,22 +834,33 @@ CONDITION_SCHEMA = vol.Any(
|
|||
OR_CONDITION_SCHEMA,
|
||||
) # type: vol.Schema
|
||||
|
||||
_SCRIPT_DELAY_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required("delay"): vol.Any(
|
||||
vol.All(time_period, positive_timedelta),
|
||||
template, template_complex)
|
||||
})
|
||||
_SCRIPT_DELAY_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required("delay"): vol.Any(
|
||||
vol.All(time_period, positive_timedelta), template, template_complex
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
_SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required("wait_template"): template,
|
||||
vol.Optional(CONF_TIMEOUT): vol.All(time_period, positive_timedelta),
|
||||
vol.Optional("continue_on_timeout"): boolean,
|
||||
})
|
||||
_SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required("wait_template"): template,
|
||||
vol.Optional(CONF_TIMEOUT): vol.All(time_period, positive_timedelta),
|
||||
vol.Optional("continue_on_timeout"): boolean,
|
||||
}
|
||||
)
|
||||
|
||||
SCRIPT_SCHEMA = vol.All(
|
||||
ensure_list,
|
||||
[vol.Any(SERVICE_SCHEMA, _SCRIPT_DELAY_SCHEMA,
|
||||
_SCRIPT_WAIT_TEMPLATE_SCHEMA, EVENT_SCHEMA, CONDITION_SCHEMA)],
|
||||
[
|
||||
vol.Any(
|
||||
SERVICE_SCHEMA,
|
||||
_SCRIPT_DELAY_SCHEMA,
|
||||
_SCRIPT_WAIT_TEMPLATE_SCHEMA,
|
||||
EVENT_SCHEMA,
|
||||
CONDITION_SCHEMA,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue