Add script + extra config validators
* Add config validation and extra validators * Address PR comments
This commit is contained in:
parent
7ed5055fa2
commit
e140e9b8ab
9 changed files with 404 additions and 88 deletions
|
@ -1,22 +1,34 @@
|
|||
"""Helpers for config validation using voluptuous."""
|
||||
import jinja2
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.entity import valid_entity_id
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util import slugify
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): str,
|
||||
CONF_SCAN_INTERVAL: vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
# Home Assistant types
|
||||
|
||||
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))
|
||||
latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90))
|
||||
longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180))
|
||||
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')
|
||||
|
||||
|
||||
def boolean(value):
|
||||
"""Validate and coerce a boolean value."""
|
||||
if isinstance(value, str):
|
||||
if value in ('1', 'true', 'yes', 'on', 'enable'):
|
||||
return True
|
||||
if value in ('0', 'false', 'no', 'off', 'disable'):
|
||||
return False
|
||||
raise vol.Invalid('invalid boolean value {}'.format(value))
|
||||
return bool(value)
|
||||
|
||||
|
||||
def entity_id(value):
|
||||
|
@ -48,22 +60,54 @@ def icon(value):
|
|||
raise vol.Invalid('Icons should start with prefix "mdi:"')
|
||||
|
||||
|
||||
def service(value):
|
||||
"""Validate service."""
|
||||
# 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))
|
||||
|
||||
|
||||
def slug(value):
|
||||
"""Validate value is a valid slug."""
|
||||
if value is None:
|
||||
raise vol.Invalid('Slug should not be None')
|
||||
value = str(value)
|
||||
slg = slugify(value)
|
||||
if value == slg:
|
||||
return value
|
||||
raise vol.Invalid('invalid slug {} (try {})'.format(value, slg))
|
||||
|
||||
|
||||
def string(value):
|
||||
"""Coerce value to string, except for None."""
|
||||
if value is not None:
|
||||
return str(value)
|
||||
raise vol.Invalid('Value should not be None')
|
||||
raise vol.Invalid('string value is None')
|
||||
|
||||
|
||||
def temperature_unit(value):
|
||||
"""Validate and transform temperature unit."""
|
||||
if isinstance(value, str):
|
||||
value = value.upper()
|
||||
if value == 'C':
|
||||
return TEMP_CELCIUS
|
||||
elif value == 'F':
|
||||
return TEMP_FAHRENHEIT
|
||||
raise vol.Invalid('Invalid temperature unit. Expected: C or F')
|
||||
value = str(value).upper()
|
||||
if value == 'C':
|
||||
return TEMP_CELCIUS
|
||||
elif value == 'F':
|
||||
return TEMP_FAHRENHEIT
|
||||
raise vol.Invalid('invalid temperature unit (expected C or F)')
|
||||
|
||||
|
||||
def template(value):
|
||||
"""Validate a jinja2 template."""
|
||||
if value is None:
|
||||
raise vol.Invalid('template value is None')
|
||||
|
||||
value = str(value)
|
||||
try:
|
||||
jinja2.Environment().parse(value)
|
||||
return value
|
||||
except jinja2.exceptions.TemplateSyntaxError as ex:
|
||||
raise vol.Invalid('invalid template ({})'.format(ex))
|
||||
|
||||
|
||||
def time_zone(value):
|
||||
|
@ -73,3 +117,93 @@ def time_zone(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')
|
||||
|
||||
|
||||
# Validator helpers
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
class DictValidator(object):
|
||||
"""Validate keys and values in a dictionary."""
|
||||
|
||||
def __init__(self, value_validator=None, key_validator=None):
|
||||
"""Initialize the dict validator."""
|
||||
if value_validator is not None:
|
||||
value_validator = vol.Schema(value_validator)
|
||||
|
||||
self.value_validator = value_validator
|
||||
|
||||
if key_validator is not None:
|
||||
key_validator = vol.Schema(key_validator)
|
||||
|
||||
self.key_validator = key_validator
|
||||
|
||||
def __call__(self, obj):
|
||||
"""Validate the dict."""
|
||||
if not isinstance(obj, dict):
|
||||
raise vol.Invalid('Expected dictionary.')
|
||||
|
||||
errors = []
|
||||
|
||||
# So we keep it an OrderedDict if it is one
|
||||
result = obj.__class__()
|
||||
|
||||
for key, value in obj.items():
|
||||
if self.key_validator is not None:
|
||||
try:
|
||||
key = self.key_validator(key)
|
||||
except vol.Invalid as ex:
|
||||
errors.append('key {} is invalid ({})'.format(key, ex))
|
||||
|
||||
if self.value_validator is not None:
|
||||
try:
|
||||
value = self.value_validator(value)
|
||||
except vol.Invalid as ex:
|
||||
errors.append(
|
||||
'key {} contains invalid value ({})'.format(key, ex))
|
||||
|
||||
if not errors:
|
||||
result[key] = value
|
||||
|
||||
if errors:
|
||||
raise vol.Invalid(
|
||||
'invalid dictionary: {}'.format(', '.join(errors)))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Adapted from:
|
||||
# https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666
|
||||
def has_at_least_one_key(keys):
|
||||
"""Validator that at least one key exists."""
|
||||
def validate(obj):
|
||||
"""Test keys exist in dict."""
|
||||
if not isinstance(obj, dict):
|
||||
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)))
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
# Schemas
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): string,
|
||||
CONF_SCAN_INTERVAL: vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
EVENT_SCHEMA = vol.Schema({
|
||||
vol.Required('event'): string,
|
||||
'event_data': dict
|
||||
})
|
||||
|
||||
SERVICE_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Exclusive('service', 'service name'): service,
|
||||
vol.Exclusive('service_template', 'service name'): string,
|
||||
vol.Exclusive('data', 'service data'): dict,
|
||||
vol.Exclusive('data_template', 'service data'): DictValidator(template),
|
||||
}), has_at_least_one_key(['service', 'service_template']))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue