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 fixpull/3332/head
parent
d6ca930427
commit
f0ec51711c
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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})
|
||||||
|
|
Loading…
Reference in New Issue