Skip to content

Commit

Permalink
Use voluptuous for input_slider, input_boolean, input_select (#3256)
Browse files Browse the repository at this point in the history
* Use voluptuous for input slider

* floats

* _setup_component

* Imperative mood

* CONFIG_SCHEMA

* None returns empty ensure_list

* allow_extra

* bool

* restore ensure_list behaviour
  • Loading branch information
kellerza authored and balloob committed Sep 23, 2016
1 parent de51cfb commit 9631179
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 185 deletions.
8 changes: 4 additions & 4 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
except vol.Invalid as ex:
log_exception(ex, domain, config)
return None

Expand All @@ -155,8 +155,8 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, p_config)
except vol.Invalid as ex:
log_exception(ex, domain, config)
return None

# Not all platform components follow same pattern for platforms
Expand All @@ -176,7 +176,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
except vol.Invalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return None
Expand Down
24 changes: 10 additions & 14 deletions homeassistant/components/input_boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,31 @@
import voluptuous as vol

from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_TOGGLE,
STATE_ON)
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_TOGGLE, STATE_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify

DOMAIN = 'input_boolean'

ENTITY_ID_FORMAT = DOMAIN + '.{}'

_LOGGER = logging.getLogger(__name__)

CONF_NAME = "name"
CONF_INITIAL = "initial"
CONF_ICON = "icon"
CONF_INITIAL = 'initial'

SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

CONFIG_SCHEMA = vol.Schema({
cv.slug: {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_INITIAL): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
}}, extra=vol.ALLOW_EXTRA)


def is_on(hass, entity_id):
"""Test if input_boolean is True."""
Expand All @@ -53,19 +57,11 @@ def toggle(hass, entity_id):

def setup(hass, config):
"""Set up input boolean."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False

component = EntityComponent(_LOGGER, DOMAIN, hass)

entities = []

for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
cfg = {}

Expand Down
52 changes: 23 additions & 29 deletions homeassistant/components/input_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@

import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify


DOMAIN = 'input_select'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)

CONF_NAME = 'name'
CONF_INITIAL = 'initial'
CONF_ICON = 'icon'
CONF_OPTIONS = 'options'

ATTR_OPTION = 'option'
Expand All @@ -34,6 +32,26 @@
})


def _cv_input_select(cfg):
"""Config validation helper for input select (Voluptuous)."""
options = cfg[CONF_OPTIONS]
state = cfg.get(CONF_INITIAL, options[0])
if state not in options:
raise vol.Invalid('initial state "{}" is not part of the options: {}'
.format(state, ','.join(options)))
return cfg


CONFIG_SCHEMA = vol.Schema({DOMAIN: {
cv.slug: vol.All({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1),
[cv.string]),
vol.Optional(CONF_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon,
}, _cv_input_select)}}, required=True, extra=vol.ALLOW_EXTRA)


def select_option(hass, entity_id, option):
"""Set input_select to False."""
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
Expand All @@ -44,39 +62,15 @@ def select_option(hass, entity_id, option):

def setup(hass, config):
"""Setup input select."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False

component = EntityComponent(_LOGGER, DOMAIN, hass)

entities = []

for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue

name = cfg.get(CONF_NAME)
options = cfg.get(CONF_OPTIONS)

if not isinstance(options, list) or len(options) == 0:
_LOGGER.warning('Key %s should be a list of options', CONF_OPTIONS)
continue

options = [str(val) for val in options]

state = cfg.get(CONF_INITIAL)

if state not in options:
state = options[0]

state = cfg.get(CONF_INITIAL, options[0])
icon = cfg.get(CONF_ICON)

entities.append(InputSelect(object_id, name, state, options, icon))

if not entities:
Expand Down
52 changes: 30 additions & 22 deletions homeassistant/components/input_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@

import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify

DOMAIN = 'input_slider'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)

CONF_NAME = 'name'
CONF_INITIAL = 'initial'
CONF_MIN = 'min'
CONF_MAX = 'max'
CONF_ICON = 'icon'
CONF_STEP = 'step'

ATTR_VALUE = 'value'
Expand All @@ -38,6 +36,33 @@
})


def _cv_input_slider(cfg):
"""Config validation helper for input slider (Voluptuous)."""
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
if minimum >= maximum:
raise vol.Invalid('Maximum ({}) is not greater than minimum ({})'
.format(minimum, maximum))
state = cfg.get(CONF_INITIAL, minimum)
if state < minimum or state > maximum:
raise vol.Invalid('Initial value {} not in range {}-{}'
.format(state, minimum, maximum))
cfg[CONF_INITIAL] = state
return cfg

CONFIG_SCHEMA = vol.Schema({DOMAIN: {
cv.slug: vol.All({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_MIN): vol.Coerce(float),
vol.Required(CONF_MAX): vol.Coerce(float),
vol.Optional(CONF_INITIAL): vol.Coerce(float),
vol.Optional(CONF_STEP, default=1): vol.All(vol.Coerce(float),
vol.Range(min=1e-3)),
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string
}, _cv_input_slider)}}, required=True, extra=vol.ALLOW_EXTRA)


def select_value(hass, entity_id, value):
"""Set input_slider to value."""
hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, {
Expand All @@ -48,36 +73,19 @@ def select_value(hass, entity_id, value):

def setup(hass, config):
"""Set up input slider."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False

component = EntityComponent(_LOGGER, DOMAIN, hass)

entities = []

for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue

name = cfg.get(CONF_NAME)
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
state = cfg.get(CONF_INITIAL, minimum)
step = cfg.get(CONF_STEP, 1)
step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON)
unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT)

if state < minimum:
state = minimum
if state > maximum:
state = maximum

entities.append(InputSlider(object_id, name, state, minimum, maximum,
step, icon, unit))

Expand Down
6 changes: 3 additions & 3 deletions homeassistant/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Helper methods for components within Home Assistant."""
import re

from typing import Any, Iterable, Tuple, List, Dict
from typing import Any, Iterable, Tuple, Sequence, Dict

from homeassistant.const import CONF_PLATFORM

# Typing Imports and TypeAlias
# pylint: disable=using-constant-test,unused-import
# pylint: disable=using-constant-test,unused-import,wrong-import-order
if False:
from logging import Logger # NOQA

Expand Down Expand Up @@ -34,7 +34,7 @@ def config_per_platform(config: ConfigType,
yield platform, item


def extract_domain_configs(config: ConfigType, domain: str) -> List[str]:
def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]:
"""Extract keys from config for given domain name."""
pattern = re.compile(r'^{}(| .+)$'.format(domain))
return [key for key in config.keys() if pattern.match(key)]
6 changes: 3 additions & 3 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from urllib.parse import urlparse

from typing import Any, Union, TypeVar, Callable, Sequence, List, Dict
from typing import Any, Union, TypeVar, Callable, Sequence, Dict

import jinja2
import voluptuous as vol
Expand Down Expand Up @@ -80,7 +80,7 @@ def isfile(value: Any) -> str:
return file_in


def ensure_list(value: Union[T, Sequence[T]]) -> List[T]:
def ensure_list(value: Union[T, Sequence[T]]) -> Sequence[T]:
"""Wrap value in list if it is not one."""
return value if isinstance(value, list) else [value]

Expand All @@ -93,7 +93,7 @@ def entity_id(value: Any) -> str:
raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value))


def entity_ids(value: Union[str, Sequence]) -> List[str]:
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')
Expand Down
Loading

2 comments on commit 9631179

@tylerstraub
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kellerza,

I think voluptious maybe have broken input_boolean for me. After last night's rebase I started getting:

16-09-23 11:53:46 homeassistant.bootstrap: Invalid config for [input_boolean]: expected a dictionary for dictionary value

@tylerstraub
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that functionality was restored by rolling back to #3354 (the commit immediately before this one).

Please sign in to comment.