Bugfix group order (#3323)

* Add ordered dict config validator

* Have group component use ordered dict config validator

* Improve config_validation testing

* update doc string config_validation.ordered_dict

* validate full dict entries

* Further simplify ordered_dict validator.

* Lint fix
pull/3332/head
Paulus Schoutsen 2016-09-11 22:25:01 -07:00
parent d6ca930427
commit f0ec51711c
4 changed files with 84 additions and 9 deletions

View File

@ -46,12 +46,12 @@ def _conf_preprocess(value):
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: {cv.match_all: vol.Schema(vol.All(_conf_preprocess, { DOMAIN: cv.ordered_dict(vol.All(_conf_preprocess, {
vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None), vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None),
CONF_VIEW: cv.boolean, CONF_VIEW: cv.boolean,
CONF_NAME: cv.string, CONF_NAME: cv.string,
CONF_ICON: cv.icon, CONF_ICON: cv.icon,
}))} }, cv.match_all))
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
# List of ON/OFF state tuples for groupable states # List of ON/OFF state tuples for groupable states

View File

@ -1,4 +1,5 @@
"""Helpers for config validation using voluptuous.""" """Helpers for config validation using voluptuous."""
from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import os import os
from urllib.parse import urlparse from urllib.parse import urlparse
@ -290,6 +291,27 @@ def url(value: Any) -> str:
raise vol.Invalid('invalid url') raise vol.Invalid('invalid url')
def ordered_dict(value_validator, key_validator=match_all):
"""Validate an ordered dict validator that maintains ordering.
value_validator will be applied to each value of the dictionary.
key_validator (optional) will be applied to each key of the dictionary.
"""
item_validator = vol.Schema({key_validator: value_validator})
def validator(value):
"""Validate ordered dict."""
config = OrderedDict()
for key, val in value.items():
v_res = item_validator({key: val})
config.update(v_res)
return config
return validator
# Validator helpers # Validator helpers
def key_dependency(key, dependency): def key_dependency(key, dependency):

View File

@ -1,5 +1,6 @@
"""The tests for the Group components.""" """The tests for the Group components."""
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
from collections import OrderedDict
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@ -220,16 +221,16 @@ 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)
_setup_component(self.hass, 'group', {'group': { group_conf = OrderedDict()
'second_group': { group_conf['second_group'] = {
'entities': 'light.Bowl, ' + test_group.entity_id, 'entities': 'light.Bowl, ' + test_group.entity_id,
'icon': 'mdi:work', 'icon': 'mdi:work',
'view': True, 'view': True,
}, }
'test_group': 'hello.world,sensor.happy', group_conf['test_group'] = 'hello.world,sensor.happy'
'empty_group': {'name': 'Empty Group', 'entities': None}, group_conf['empty_group'] = {'name': 'Empty Group', 'entities': None}
}
}) _setup_component(self.hass, 'group', {'group': group_conf})
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'))
@ -241,6 +242,7 @@ class TestComponentsGroup(unittest.TestCase):
group_state.attributes.get(ATTR_ICON)) group_state.attributes.get(ATTR_ICON))
self.assertTrue(group_state.attributes.get(group.ATTR_VIEW)) self.assertTrue(group_state.attributes.get(group.ATTR_VIEW))
self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) self.assertTrue(group_state.attributes.get(ATTR_HIDDEN))
self.assertEqual(1, group_state.attributes.get(group.ATTR_ORDER))
group_state = self.hass.states.get( group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('test_group')) group.ENTITY_ID_FORMAT.format('test_group'))
@ -251,6 +253,7 @@ class TestComponentsGroup(unittest.TestCase):
self.assertIsNone(group_state.attributes.get(ATTR_ICON)) self.assertIsNone(group_state.attributes.get(ATTR_ICON))
self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW)) self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW))
self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN)) self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
self.assertEqual(2, group_state.attributes.get(group.ATTR_ORDER))
def test_groups_get_unique_names(self): def test_groups_get_unique_names(self):
"""Two groups with same name should both have a unique entity id.""" """Two groups with same name should both have a unique entity id."""

View File

@ -1,3 +1,5 @@
"""Test config validators."""
from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import os import os
import tempfile import tempfile
@ -367,3 +369,51 @@ def test_has_at_least_one_key():
for value in ({'beer': None}, {'soda': None}): for value in ({'beer': None}, {'soda': None}):
schema(value) schema(value)
def test_ordered_dict_order():
"""Test ordered_dict validator."""
schema = vol.Schema(cv.ordered_dict(int, cv.string))
val = OrderedDict()
val['first'] = 1
val['second'] = 2
validated = schema(val)
assert isinstance(validated, OrderedDict)
assert ['first', 'second'] == list(validated.keys())
def test_ordered_dict_key_validator():
"""Test ordered_dict key validator."""
schema = vol.Schema(cv.ordered_dict(cv.match_all, cv.string))
with pytest.raises(vol.Invalid):
schema({None: 1})
schema({'hello': 'world'})
schema = vol.Schema(cv.ordered_dict(cv.match_all, int))
with pytest.raises(vol.Invalid):
schema({'hello': 1})
schema({1: 'works'})
def test_ordered_dict_value_validator():
"""Test ordered_dict validator."""
schema = vol.Schema(cv.ordered_dict(cv.string))
with pytest.raises(vol.Invalid):
schema({'hello': None})
schema({'hello': 'world'})
schema = vol.Schema(cv.ordered_dict(int))
with pytest.raises(vol.Invalid):
schema({'hello': 'world'})
schema({'hello': 5})