Add initial config validation

This commit is contained in:
Paulus Schoutsen 2016-03-27 18:48:51 -07:00
parent 0549bc0290
commit 5baa98b79f
24 changed files with 349 additions and 72 deletions

View file

@ -244,6 +244,9 @@ def setup_and_run_hass(config_dir, args, top_process=False):
config_file, daemon=args.daemon, verbose=args.verbose, config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days) skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
if hass is None:
return
if args.open_ui: if args.open_ui:
def open_browser(event): def open_browser(event):
"""Open the webinterface in a browser.""" """Open the webinterface in a browser."""

View file

@ -8,6 +8,8 @@ import sys
from collections import defaultdict from collections import defaultdict
from threading import RLock from threading import RLock
import voluptuous as vol
import homeassistant.components as core_components import homeassistant.components as core_components
import homeassistant.components.group as group import homeassistant.components.group as group
import homeassistant.config as config_util import homeassistant.config as config_util
@ -20,7 +22,8 @@ from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED, CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, __version__) TEMP_CELCIUS, TEMP_FAHRENHEIT, __version__)
from homeassistant.helpers import event_decorators, service from homeassistant.helpers import (
event_decorators, service, config_per_platform)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -72,7 +75,7 @@ def _handle_requirements(hass, component, name):
def _setup_component(hass, domain, config): def _setup_component(hass, domain, config):
"""Setup a component for Home Assistant.""" """Setup a component for Home Assistant."""
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements,too-many-branches
if domain in hass.config.components: if domain in hass.config.components:
return True return True
@ -96,6 +99,25 @@ def _setup_component(hass, domain, config):
domain, ", ".join(missing_deps)) domain, ", ".join(missing_deps))
return False return False
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid config for [%s]: %s', domain, ex)
return False
if hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for _, platform in config_per_platform(config, domain):
try:
platforms.append(component.PLATFORM_SCHEMA(platform))
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid platform config for [%s]: %s. %s',
domain, ex, platform)
return False
config = {domain: platforms}
if not _handle_requirements(hass, component, domain): if not _handle_requirements(hass, component, domain):
return False return False
@ -176,8 +198,14 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
mount_local_lib_path(config_dir) mount_local_lib_path(config_dir)
try:
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
config.get(core.DOMAIN, {})))
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid config for [homeassistant]: %s', ex)
return None
process_ha_config_upgrade(hass) process_ha_config_upgrade(hass)
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log: if enable_log:
enable_logging(hass, verbose, daemon, log_rotate_days) enable_logging(hass, verbose, daemon, log_rotate_days)
@ -336,40 +364,28 @@ def process_ha_core_config(hass, config):
else: else:
_LOGGER.error('Received invalid time zone %s', time_zone_str) _LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr, typ in ((CONF_LATITUDE, 'latitude', float), for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude', float), (CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name', str)): (CONF_NAME, 'location_name')):
if key in config: if key in config:
try: setattr(hac, attr, config[key])
setattr(hac, attr, typ(config[key]))
except ValueError:
_LOGGER.error('Received invalid %s value for %s: %s',
typ.__name__, key, attr)
set_time_zone(config.get(CONF_TIME_ZONE)) if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
customize = config.get(CONF_CUSTOMIZE) for entity_id, attrs in config.get(CONF_CUSTOMIZE).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if isinstance(customize, dict):
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
if not isinstance(attrs, dict):
continue
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if CONF_TEMPERATURE_UNIT in config: if CONF_TEMPERATURE_UNIT in config:
unit = config[CONF_TEMPERATURE_UNIT] hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
if unit == 'C':
hac.temperature_unit = TEMP_CELCIUS
elif unit == 'F':
hac.temperature_unit = TEMP_FAHRENHEIT
# If we miss some of the needed values, auto detect them # If we miss some of the needed values, auto detect them
if None not in ( if None not in (
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone): hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
return return
_LOGGER.info('Auto detecting location and temperature unit') _LOGGER.warning('Incomplete core config. Auto detecting location and '
'temperature unit')
info = loc_util.detect_location_info() info = loc_util.detect_location_info()

View file

@ -11,6 +11,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import ( from homeassistant.components import (
bloomsky, mysensors, zwave, vera, wemo, wink) bloomsky, mysensors, zwave, vera, wemo, wink)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
DOMAIN = 'binary_sensor' DOMAIN = 'binary_sensor'
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30

View file

@ -15,6 +15,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import bloomsky from homeassistant.components import bloomsky
from homeassistant.const import HTTP_OK, HTTP_NOT_FOUND, ATTR_ENTITY_ID from homeassistant.const import HTTP_OK, HTTP_NOT_FOUND, ATTR_ENTITY_ID
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
DOMAIN = 'camera' DOMAIN = 'camera'

View file

@ -17,6 +17,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform from homeassistant.helpers import config_per_platform
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -129,8 +130,7 @@ def setup(hass, config):
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up platform %s', p_type) _LOGGER.exception('Error setting up platform %s', p_type)
for p_type, p_config in \ for p_type, p_config in config_per_platform(config, DOMAIN):
config_per_platform(config, DOMAIN, _LOGGER):
setup_platform(p_type, p_config) setup_platform(p_type, p_config)
def device_tracker_discovered(service, info): def device_tracker_discovered(service, info):

View file

@ -10,7 +10,7 @@ import os
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import ( from homeassistant.const import (
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN, STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
ATTR_ENTITY_ID) ATTR_ENTITY_ID)

View file

@ -6,6 +6,8 @@ https://home-assistant.io/components/group/
""" """
import threading import threading
import voluptuous as vol
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
@ -14,6 +16,7 @@ from homeassistant.const import (
from homeassistant.helpers.entity import ( from homeassistant.helpers.entity import (
Entity, generate_entity_id, split_entity_id) Entity, generate_entity_id, split_entity_id)
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
DOMAIN = 'group' DOMAIN = 'group'
@ -26,6 +29,38 @@ ATTR_AUTO = 'auto'
ATTR_ORDER = 'order' ATTR_ORDER = 'order'
ATTR_VIEW = 'view' ATTR_VIEW = 'view'
def _conf_preprocess(value):
"""Preprocess alternative configuration formats."""
if isinstance(value, (str, list)):
value = {CONF_ENTITIES: value}
return value
_SINGLE_GROUP_CONFIG = vol.Schema(vol.All(_conf_preprocess, {
vol.Required(CONF_ENTITIES): cv.entity_ids,
CONF_VIEW: bool,
CONF_NAME: str,
CONF_ICON: cv.icon,
}))
def _group_dict(value):
"""Validate a dictionary of group definitions."""
config = {}
for key, group in value.items():
try:
config[key] = _SINGLE_GROUP_CONFIG(group)
except vol.MultipleInvalid as ex:
raise vol.Invalid('Group {} is invalid: {}'.format(key, ex))
return config
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(dict, _group_dict)
}, extra=True)
# List of ON/OFF state tuples for groupable states # List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME), _GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
(STATE_OPEN, STATE_CLOSED)] (STATE_OPEN, STATE_CLOSED)]
@ -108,17 +143,11 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config): def setup(hass, config):
"""Setup all groups found definded in the configuration.""" """Setup all groups found definded in the configuration."""
for object_id, conf in config.get(DOMAIN, {}).items(): for object_id, conf in config.get(DOMAIN, {}).items():
if not isinstance(conf, dict):
conf = {CONF_ENTITIES: conf}
name = conf.get(CONF_NAME, object_id) name = conf.get(CONF_NAME, object_id)
entity_ids = conf.get(CONF_ENTITIES) entity_ids = conf[CONF_ENTITIES]
icon = conf.get(CONF_ICON) icon = conf.get(CONF_ICON)
view = conf.get(CONF_VIEW) view = conf.get(CONF_VIEW)
if isinstance(entity_ids, str):
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
Group(hass, name, entity_ids, icon=icon, view=view, Group(hass, name, entity_ids, icon=icon, view=view,
object_id=object_id) object_id=object_id)

View file

@ -17,6 +17,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID) ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.color as color_util import homeassistant.util.color as color_util

View file

@ -11,7 +11,7 @@ import os
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)

View file

@ -11,6 +11,7 @@ from homeassistant.components import discovery
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import ( from homeassistant.const import (
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE, STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE,
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,

View file

@ -10,8 +10,8 @@ import os
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_per_platform from homeassistant.helpers import config_per_platform, template
from homeassistant.helpers import template from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
@ -51,7 +51,7 @@ def setup(hass, config):
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml')) os.path.join(os.path.dirname(__file__), 'services.yaml'))
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER): for platform, p_config in config_per_platform(config, DOMAIN):
notify_implementation = bootstrap.prepare_setup_platform( notify_implementation = bootstrap.prepare_setup_platform(
hass, config, DOMAIN, platform) hass, config, DOMAIN, platform)

View file

@ -10,6 +10,7 @@ import logging
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import group from homeassistant.components import group
from homeassistant.const import ( from homeassistant.const import (
SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP, SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP,

View file

@ -7,6 +7,7 @@ https://home-assistant.io/components/sensor/
import logging import logging
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import ( from homeassistant.components import (
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors,
bloomsky, vera) bloomsky, vera)

View file

@ -11,7 +11,7 @@ import os
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
ATTR_ENTITY_ID) ATTR_ENTITY_ID)

View file

@ -13,6 +13,7 @@ from homeassistant.config import load_yaml_config_file
import homeassistant.util as util import homeassistant.util as util
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.temperature import convert from homeassistant.helpers.temperature import convert
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import ecobee from homeassistant.components import ecobee
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,

View file

@ -1,13 +1,18 @@
"""Module to help with parsing and generating configuration files.""" """Module to help with parsing and generating configuration files."""
import logging import logging
import os import os
from types import MappingProxyType
import voluptuous as vol
import homeassistant.util.location as loc_util import homeassistant.util.location as loc_util
from homeassistant.const import ( from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT,
CONF_TIME_ZONE) CONF_TIME_ZONE, CONF_CUSTOMIZE)
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.yaml import load_yaml from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import valid_entity_id
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,6 +42,31 @@ DEFAULT_COMPONENTS = {
} }
def _valid_customize(value):
"""Config validator for customize."""
if not isinstance(value, dict):
raise vol.Invalid('Expected dictionary')
for key, val in value.items():
if not valid_entity_id(key):
raise vol.Invalid('Invalid entity ID: {}'.format(key))
if not isinstance(val, dict):
raise vol.Invalid('Value of {} is not a dictionary'.format(key))
return value
CORE_CONFIG_SCHEMA = vol.Schema({
CONF_NAME: vol.Coerce(str),
CONF_LATITUDE: cv.latitude,
CONF_LONGITUDE: cv.longitude,
CONF_TEMPERATURE_UNIT: cv.temperature_unit,
CONF_TIME_ZONE: cv.time_zone,
vol.Required(CONF_CUSTOMIZE,
default=MappingProxyType({})): _valid_customize,
})
def get_default_config_dir(): def get_default_config_dir():
"""Put together the default configuration directory based on OS.""" """Put together the default configuration directory based on OS."""
data_dir = os.getenv('APPDATA') if os.name == "nt" \ data_dir = os.getenv('APPDATA') if os.name == "nt" \

View file

@ -29,30 +29,18 @@ def validate_config(config, items, logger):
return not errors_found return not errors_found
def config_per_platform(config, domain, logger): def config_per_platform(config, domain):
"""Generator to break a component config into different platforms. """Generator to break a component config into different platforms.
For example, will find 'switch', 'switch 2', 'switch 3', .. etc For example, will find 'switch', 'switch 2', 'switch 3', .. etc
""" """
config_key = domain
found = 1
for config_key in extract_domain_configs(config, domain): for config_key in extract_domain_configs(config, domain):
platform_config = config[config_key] platform_config = config[config_key]
if not isinstance(platform_config, list): if not isinstance(platform_config, list):
platform_config = [platform_config] platform_config = [platform_config]
for item in platform_config: for item in platform_config:
platform_type = item.get(CONF_PLATFORM) yield item.get(CONF_PLATFORM), item
if platform_type is None:
logger.warning('No platform specified for %s', config_key)
continue
yield platform_type, item
found += 1
config_key = "{} {}".format(domain, found)
def extract_domain_configs(config, domain): def extract_domain_configs(config, domain):

View file

@ -0,0 +1,65 @@
"""Helpers for config validation using voluptuous."""
import voluptuous as vol
from homeassistant.const import (
CONF_PLATFORM, TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import valid_entity_id
import homeassistant.util.dt as dt_util
# pylint: disable=invalid-name
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): str,
}, extra=vol.ALLOW_EXTRA)
latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90))
longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180))
def entity_id(value):
"""Validate Entity ID."""
if valid_entity_id(value):
return value
raise vol.Invalid('Entity ID {} does not match format <domain>.<object_id>'
.format(value))
def entity_ids(value):
"""Validate Entity IDs."""
if isinstance(value, str):
value = [ent_id.strip() for ent_id in value.split(',')]
for ent_id in value:
entity_id(ent_id)
return value
def icon(value):
"""Validate icon."""
value = str(value)
if value.startswith('mdi:'):
return value
raise vol.Invalid('Icons should start with prefix "mdi:"')
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')
def time_zone(value):
"""Validate timezone."""
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')

View file

@ -49,9 +49,7 @@ class EntityComponent(object):
self.config = config self.config = config
# Look in config for Domain, Domain 2, Domain 3 etc and load them # Look in config for Domain, Domain 2, Domain 3 etc and load them
for p_type, p_config in \ for p_type, p_config in config_per_platform(config, self.domain):
config_per_platform(config, self.domain, self.logger):
self._setup_platform(p_type, p_config) self._setup_platform(p_type, p_config)
if self.discovery_platforms: if self.discovery_platforms:

View file

@ -5,6 +5,7 @@ pytz>=2015.4
pip>=7.0.0 pip>=7.0.0
vincenty==0.1.3 vincenty==0.1.3
jinja2>=2.8 jinja2>=2.8
voluptuous==0.8.9
# homeassistant.components.isy994 # homeassistant.components.isy994
PyISY==1.0.5 PyISY==1.0.5

View file

@ -17,6 +17,7 @@ REQUIRES = [
'pip>=7.0.0', 'pip>=7.0.0',
'vincenty==0.1.3', 'vincenty==0.1.3',
'jinja2>=2.8', 'jinja2>=2.8',
'voluptuous==0.8.9',
] ]
setup( setup(

View file

@ -2,6 +2,7 @@
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
import unittest import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN, STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN,
ATTR_ASSUMED_STATE, ) ATTR_ASSUMED_STATE, )
@ -218,19 +219,15 @@ class TestComponentsGroup(unittest.TestCase):
test_group = group.Group( test_group = group.Group(
self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False)
self.assertTrue( _setup_component(self.hass, 'group', {'group': {
group.setup( 'second_group': {
self.hass, 'entities': 'light.Bowl, ' + test_group.entity_id,
{ 'icon': 'mdi:work',
group.DOMAIN: { 'view': True,
'second_group': { },
'entities': 'light.Bowl, ' + test_group.entity_id, 'test_group': 'hello.world,sensor.happy',
'icon': 'mdi:work', }
'view': True, })
},
'test_group': 'hello.world,sensor.happy',
}
}))
group_state = self.hass.states.get( group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('second_group')) group.ENTITY_ID_FORMAT.format('second_group'))

View file

@ -0,0 +1,114 @@
import pytest
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
def test_latitude():
"""Test latitude validation."""
schema = vol.Schema(cv.latitude)
for value in ('invalid', None, -91, 91, '-91', '91', '123.01A'):
with pytest.raises(vol.MultipleInvalid):
schema(value)
for value in ('-89', 89, '12.34'):
schema(value)
def test_longitude():
"""Test longitude validation."""
schema = vol.Schema(cv.longitude)
for value in ('invalid', None, -181, 181, '-181', '181', '123.01A'):
with pytest.raises(vol.MultipleInvalid):
schema(value)
for value in ('-179', 179, '12.34'):
schema(value)
def test_icon():
"""Test icon validation."""
schema = vol.Schema(cv.icon)
for value in (False, 'work', 'icon:work'):
with pytest.raises(vol.MultipleInvalid):
schema(value)
schema('mdi:work')
def test_platform_config():
"""Test platform config validation."""
for value in (
{'platform': 1},
{},
{'hello': 'world'},
):
with pytest.raises(vol.MultipleInvalid):
cv.PLATFORM_SCHEMA(value)
for value in (
{'platform': 'mqtt'},
{'platform': 'mqtt', 'beer': 'yes'},
):
cv.PLATFORM_SCHEMA(value)
def test_entity_id():
"""Test entity ID validation."""
schema = vol.Schema(cv.entity_id)
with pytest.raises(vol.MultipleInvalid):
schema('invalid_entity')
schema('sensor.light')
def test_entity_ids():
"""Test entity ID validation."""
schema = vol.Schema(cv.entity_ids)
for value in (
'invalid_entity',
'sensor.light,sensor_invalid',
['invalid_entity'],
['sensor.light', 'sensor_invalid'],
['sensor.light,sensor_invalid'],
):
with pytest.raises(vol.MultipleInvalid):
schema(value)
for value in (
[],
['sensor.light'],
'sensor.light'
):
schema(value)
assert schema('sensor.light, light.kitchen ') == [
'sensor.light', 'light.kitchen'
]
def test_temperature_unit():
"""Test temperature unit validation."""
schema = vol.Schema(cv.temperature_unit)
with pytest.raises(vol.MultipleInvalid):
schema('K')
schema('C')
schema('F')
def test_time_zone():
"""Test time zone validation."""
schema = vol.Schema(cv.time_zone)
with pytest.raises(vol.MultipleInvalid):
schema('America/Do_Not_Exist')
schema('America/Los_Angeles')
schema('UTC')

View file

@ -4,6 +4,9 @@ import unittest
import unittest.mock as mock import unittest.mock as mock
import os import os
import pytest
from voluptuous import MultipleInvalid
from homeassistant.core import DOMAIN, HomeAssistantError from homeassistant.core import DOMAIN, HomeAssistantError
import homeassistant.config as config_util import homeassistant.config as config_util
from homeassistant.const import ( from homeassistant.const import (
@ -138,3 +141,28 @@ class TestConfig(unittest.TestCase):
config_util.create_default_config( config_util.create_default_config(
os.path.join(CONFIG_DIR, 'non_existing_dir/'), False)) os.path.join(CONFIG_DIR, 'non_existing_dir/'), False))
self.assertTrue(mock_print.called) self.assertTrue(mock_print.called)
def test_core_config_schema(self):
for value in (
{'temperature_unit': 'K'},
{'time_zone': 'non-exist'},
{'latitude': '91'},
{'longitude': -181},
{'customize': 'bla'},
{'customize': {'invalid_entity_id': {}}},
{'customize': {'light.sensor': 100}},
):
with pytest.raises(MultipleInvalid):
config_util.CORE_CONFIG_SCHEMA(value)
config_util.CORE_CONFIG_SCHEMA({
'name': 'Test name',
'latitude': '-23.45',
'longitude': '123.45',
'temperature_unit': 'c',
'customize': {
'sensor.temperature': {
'hidden': True,
},
},
})