Simplify customize (#6007)
* Simplify customize * Maintain glob order * Have glob overrule domain
This commit is contained in:
parent
eb9400de4c
commit
235d0057b1
9 changed files with 163 additions and 266 deletions
|
@ -13,9 +13,9 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
|
||||||
CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC,
|
CONF_TIME_ZONE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC,
|
||||||
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
|
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
|
||||||
__version__)
|
__version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB)
|
||||||
from homeassistant.core import DOMAIN as CONF_CORE
|
from homeassistant.core import DOMAIN as CONF_CORE
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
|
@ -23,13 +23,14 @@ from homeassistant.util.yaml import load_yaml
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util import dt as date_util, location as loc_util
|
from homeassistant.util import dt as date_util, location as loc_util
|
||||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||||
from homeassistant.helpers import customize
|
from homeassistant.helpers.entity_values import EntityValues
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
YAML_CONFIG_FILE = 'configuration.yaml'
|
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||||
VERSION_FILE = '.HA_VERSION'
|
VERSION_FILE = '.HA_VERSION'
|
||||||
CONFIG_DIR_NAME = '.homeassistant'
|
CONFIG_DIR_NAME = '.homeassistant'
|
||||||
|
DATA_CUSTOMIZE = 'hass_customize'
|
||||||
|
|
||||||
DEFAULT_CORE_CONFIG = (
|
DEFAULT_CORE_CONFIG = (
|
||||||
# Tuples (attribute, default, auto detect property, description)
|
# Tuples (attribute, default, auto detect property, description)
|
||||||
|
@ -96,7 +97,16 @@ PACKAGES_CONFIG_SCHEMA = vol.Schema({
|
||||||
{cv.slug: vol.Any(dict, list)}) # Only slugs for component names
|
{cv.slug: vol.Any(dict, list)}) # Only slugs for component names
|
||||||
})
|
})
|
||||||
|
|
||||||
CORE_CONFIG_SCHEMA = vol.Schema({
|
CUSTOMIZE_CONFIG_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(CONF_CUSTOMIZE, default={}):
|
||||||
|
vol.Schema({cv.entity_id: dict}),
|
||||||
|
vol.Optional(CONF_CUSTOMIZE_DOMAIN, default={}):
|
||||||
|
vol.Schema({cv.string: dict}),
|
||||||
|
vol.Optional(CONF_CUSTOMIZE_GLOB, default={}):
|
||||||
|
vol.Schema({cv.string: dict}),
|
||||||
|
})
|
||||||
|
|
||||||
|
CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({
|
||||||
CONF_NAME: vol.Coerce(str),
|
CONF_NAME: vol.Coerce(str),
|
||||||
CONF_LATITUDE: cv.latitude,
|
CONF_LATITUDE: cv.latitude,
|
||||||
CONF_LONGITUDE: cv.longitude,
|
CONF_LONGITUDE: cv.longitude,
|
||||||
|
@ -104,7 +114,6 @@ CORE_CONFIG_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit,
|
vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit,
|
||||||
CONF_UNIT_SYSTEM: cv.unit_system,
|
CONF_UNIT_SYSTEM: cv.unit_system,
|
||||||
CONF_TIME_ZONE: cv.time_zone,
|
CONF_TIME_ZONE: cv.time_zone,
|
||||||
vol.Optional(CONF_CUSTOMIZE, default=[]): customize.CUSTOMIZE_SCHEMA,
|
|
||||||
vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA,
|
vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -289,9 +298,29 @@ def async_process_ha_core_config(hass, config):
|
||||||
if CONF_TIME_ZONE in config:
|
if CONF_TIME_ZONE in config:
|
||||||
set_time_zone(config.get(CONF_TIME_ZONE))
|
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||||
|
|
||||||
merged_customize = merge_packages_customize(
|
# Customize
|
||||||
config[CONF_CUSTOMIZE], config[CONF_PACKAGES])
|
cust_exact = dict(config[CONF_CUSTOMIZE])
|
||||||
customize.set_customize(hass, CONF_CORE, merged_customize)
|
cust_domain = dict(config[CONF_CUSTOMIZE_DOMAIN])
|
||||||
|
cust_glob = dict(config[CONF_CUSTOMIZE_GLOB])
|
||||||
|
|
||||||
|
for name, pkg in config[CONF_PACKAGES].items():
|
||||||
|
pkg_cust = pkg.get(CONF_CORE)
|
||||||
|
|
||||||
|
if pkg_cust is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
pkg_cust = CUSTOMIZE_CONFIG_SCHEMA(pkg_cust)
|
||||||
|
except vol.Invalid:
|
||||||
|
_LOGGER.warning('Package %s contains invalid customize', name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
cust_exact.update(pkg_cust[CONF_CUSTOMIZE])
|
||||||
|
cust_domain.update(pkg_cust[CONF_CUSTOMIZE_DOMAIN])
|
||||||
|
cust_glob.update(pkg_cust[CONF_CUSTOMIZE_GLOB])
|
||||||
|
|
||||||
|
hass.data[DATA_CUSTOMIZE] = \
|
||||||
|
EntityValues(cust_exact, cust_domain, cust_glob)
|
||||||
|
|
||||||
if CONF_UNIT_SYSTEM in config:
|
if CONF_UNIT_SYSTEM in config:
|
||||||
if config[CONF_UNIT_SYSTEM] == CONF_UNIT_SYSTEM_IMPERIAL:
|
if config[CONF_UNIT_SYSTEM] == CONF_UNIT_SYSTEM_IMPERIAL:
|
||||||
|
@ -446,20 +475,6 @@ def merge_packages_config(config, packages):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def merge_packages_customize(core_customize, packages):
|
|
||||||
"""Merge customize from packages."""
|
|
||||||
schema = vol.Schema({
|
|
||||||
vol.Optional(CONF_CORE): vol.Schema({
|
|
||||||
CONF_CUSTOMIZE: customize.CUSTOMIZE_SCHEMA}),
|
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
|
||||||
|
|
||||||
cust = list(core_customize)
|
|
||||||
for pkg in packages.values():
|
|
||||||
conf = schema(pkg)
|
|
||||||
cust.extend(conf.get(CONF_CORE, {}).get(CONF_CUSTOMIZE, []))
|
|
||||||
return cust
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_check_ha_config_file(hass):
|
def async_check_ha_config_file(hass):
|
||||||
"""Check if HA config file valid.
|
"""Check if HA config file valid.
|
||||||
|
|
|
@ -77,6 +77,8 @@ CONF_COMMAND_STOP = 'command_stop'
|
||||||
CONF_CONDITION = 'condition'
|
CONF_CONDITION = 'condition'
|
||||||
CONF_COVERS = 'covers'
|
CONF_COVERS = 'covers'
|
||||||
CONF_CUSTOMIZE = 'customize'
|
CONF_CUSTOMIZE = 'customize'
|
||||||
|
CONF_CUSTOMIZE_DOMAIN = 'customize_domain'
|
||||||
|
CONF_CUSTOMIZE_GLOB = 'customize_glob'
|
||||||
CONF_DEVICE = 'device'
|
CONF_DEVICE = 'device'
|
||||||
CONF_DEVICE_CLASS = 'device_class'
|
CONF_DEVICE_CLASS = 'device_class'
|
||||||
CONF_DEVICES = 'devices'
|
CONF_DEVICES = 'devices'
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
"""A helper module for customization."""
|
|
||||||
import collections
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
import fnmatch
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.const import CONF_ENTITY_ID
|
|
||||||
from homeassistant.core import HomeAssistant, split_entity_id
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
_OVERWRITE_KEY_FORMAT = '{}.overwrite'
|
|
||||||
_OVERWRITE_CACHE_KEY_FORMAT = '{}.overwrite_cache'
|
|
||||||
|
|
||||||
_CUSTOMIZE_SCHEMA_ENTRY = vol.Schema({
|
|
||||||
vol.Required(CONF_ENTITY_ID): vol.All(
|
|
||||||
cv.ensure_list_csv, vol.Length(min=1), [vol.Schema(str)], [vol.Lower])
|
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
|
||||||
|
|
||||||
|
|
||||||
def _convert_old_config(inp: Any) -> List:
|
|
||||||
if not isinstance(inp, dict):
|
|
||||||
return cv.ensure_list(inp)
|
|
||||||
if CONF_ENTITY_ID in inp:
|
|
||||||
return [inp] # sigle entry
|
|
||||||
res = []
|
|
||||||
|
|
||||||
inp = vol.Schema({cv.match_all: dict})(inp)
|
|
||||||
for key, val in inp.items():
|
|
||||||
val = dict(val)
|
|
||||||
val[CONF_ENTITY_ID] = key
|
|
||||||
res.append(val)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
CUSTOMIZE_SCHEMA = vol.All(_convert_old_config, [_CUSTOMIZE_SCHEMA_ENTRY])
|
|
||||||
|
|
||||||
|
|
||||||
def set_customize(
|
|
||||||
hass: HomeAssistant, domain: str, customize: List[Dict]) -> None:
|
|
||||||
"""Overwrite all current customize settings.
|
|
||||||
|
|
||||||
Async friendly.
|
|
||||||
"""
|
|
||||||
hass.data[_OVERWRITE_KEY_FORMAT.format(domain)] = customize
|
|
||||||
hass.data[_OVERWRITE_CACHE_KEY_FORMAT.format(domain)] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def get_overrides(hass: HomeAssistant, domain: str, entity_id: str) -> Dict:
|
|
||||||
"""Return a dictionary of overrides related to entity_id.
|
|
||||||
|
|
||||||
Whole-domain overrides are of lowest priorities,
|
|
||||||
then glob on entity ID, and finally exact entity_id
|
|
||||||
matches are of highest priority.
|
|
||||||
|
|
||||||
The lookups are cached.
|
|
||||||
"""
|
|
||||||
cache_key = _OVERWRITE_CACHE_KEY_FORMAT.format(domain)
|
|
||||||
if cache_key in hass.data and entity_id in hass.data[cache_key]:
|
|
||||||
return hass.data[cache_key][entity_id]
|
|
||||||
overwrite_key = _OVERWRITE_KEY_FORMAT.format(domain)
|
|
||||||
if overwrite_key not in hass.data:
|
|
||||||
return {}
|
|
||||||
domain_result = {} # type: Dict[str, Any]
|
|
||||||
glob_result = {} # type: Dict[str, Any]
|
|
||||||
exact_result = {} # type: Dict[str, Any]
|
|
||||||
domain = split_entity_id(entity_id)[0]
|
|
||||||
|
|
||||||
def clean_entry(entry: Dict) -> Dict:
|
|
||||||
"""Clean up entity-matching keys."""
|
|
||||||
entry.pop(CONF_ENTITY_ID, None)
|
|
||||||
return entry
|
|
||||||
|
|
||||||
def deep_update(target: Dict, source: Dict) -> None:
|
|
||||||
"""Deep update a dictionary."""
|
|
||||||
for key, value in source.items():
|
|
||||||
if isinstance(value, collections.Mapping):
|
|
||||||
updated_value = target.get(key, {})
|
|
||||||
# If the new value is map, but the old value is not -
|
|
||||||
# overwrite the old value.
|
|
||||||
if not isinstance(updated_value, collections.Mapping):
|
|
||||||
updated_value = {}
|
|
||||||
deep_update(updated_value, value)
|
|
||||||
target[key] = updated_value
|
|
||||||
else:
|
|
||||||
target[key] = source[key]
|
|
||||||
|
|
||||||
for rule in hass.data[overwrite_key]:
|
|
||||||
if CONF_ENTITY_ID in rule:
|
|
||||||
entities = rule[CONF_ENTITY_ID]
|
|
||||||
if domain in entities:
|
|
||||||
deep_update(domain_result, rule)
|
|
||||||
if entity_id in entities:
|
|
||||||
deep_update(exact_result, rule)
|
|
||||||
for entity_id_glob in entities:
|
|
||||||
if entity_id_glob == entity_id:
|
|
||||||
continue
|
|
||||||
if fnmatch.fnmatchcase(entity_id, entity_id_glob):
|
|
||||||
deep_update(glob_result, rule)
|
|
||||||
break
|
|
||||||
result = {}
|
|
||||||
deep_update(result, clean_entry(domain_result))
|
|
||||||
deep_update(result, clean_entry(glob_result))
|
|
||||||
deep_update(result, clean_entry(exact_result))
|
|
||||||
if cache_key not in hass.data:
|
|
||||||
hass.data[cache_key] = {}
|
|
||||||
hass.data[cache_key][entity_id] = result
|
|
||||||
return result
|
|
|
@ -11,12 +11,12 @@ from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
|
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
|
||||||
STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||||
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, ATTR_DEVICE_CLASS)
|
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, ATTR_DEVICE_CLASS)
|
||||||
from homeassistant.core import HomeAssistant, DOMAIN as CORE_DOMAIN
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.config import DATA_CUSTOMIZE
|
||||||
from homeassistant.exceptions import NoEntitySpecifiedError
|
from homeassistant.exceptions import NoEntitySpecifiedError
|
||||||
from homeassistant.util import ensure_unique_string, slugify
|
from homeassistant.util import ensure_unique_string, slugify
|
||||||
from homeassistant.util.async import (
|
from homeassistant.util.async import (
|
||||||
run_coroutine_threadsafe, run_callback_threadsafe)
|
run_coroutine_threadsafe, run_callback_threadsafe)
|
||||||
from homeassistant.helpers.customize import get_overrides
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -209,8 +209,6 @@ class Entity(object):
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
yield from self.async_update()
|
yield from self.async_update()
|
||||||
else:
|
else:
|
||||||
# PS: Run this in our own thread pool once we have
|
|
||||||
# future support?
|
|
||||||
yield from self.hass.loop.run_in_executor(None, self.update)
|
yield from self.hass.loop.run_in_executor(None, self.update)
|
||||||
|
|
||||||
start = timer()
|
start = timer()
|
||||||
|
@ -253,7 +251,8 @@ class Entity(object):
|
||||||
end - start)
|
end - start)
|
||||||
|
|
||||||
# Overwrite properties that have been set in the config file.
|
# Overwrite properties that have been set in the config file.
|
||||||
attr.update(get_overrides(self.hass, CORE_DOMAIN, self.entity_id))
|
if DATA_CUSTOMIZE in self.hass.data:
|
||||||
|
attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id))
|
||||||
|
|
||||||
# Remove hidden property if false so it won't show up.
|
# Remove hidden property if false so it won't show up.
|
||||||
if not attr.get(ATTR_HIDDEN, True):
|
if not attr.get(ATTR_HIDDEN, True):
|
||||||
|
|
46
homeassistant/helpers/entity_values.py
Normal file
46
homeassistant/helpers/entity_values.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""A class to hold entity values."""
|
||||||
|
from collections import OrderedDict
|
||||||
|
import fnmatch
|
||||||
|
import re
|
||||||
|
|
||||||
|
from homeassistant.core import split_entity_id
|
||||||
|
|
||||||
|
|
||||||
|
class EntityValues(object):
|
||||||
|
"""Class to store entity id based values."""
|
||||||
|
|
||||||
|
def __init__(self, exact=None, domain=None, glob=None):
|
||||||
|
"""Initialize an EntityConfigDict."""
|
||||||
|
self._cache = {}
|
||||||
|
self._exact = exact
|
||||||
|
self._domain = domain
|
||||||
|
|
||||||
|
if glob is None:
|
||||||
|
compiled = None
|
||||||
|
else:
|
||||||
|
compiled = OrderedDict()
|
||||||
|
for key, value in glob.items():
|
||||||
|
compiled[re.compile(fnmatch.translate(key))] = value
|
||||||
|
|
||||||
|
self._glob = compiled
|
||||||
|
|
||||||
|
def get(self, entity_id):
|
||||||
|
"""Get config for an entity id."""
|
||||||
|
if entity_id in self._cache:
|
||||||
|
return self._cache[entity_id]
|
||||||
|
|
||||||
|
domain, _ = split_entity_id(entity_id)
|
||||||
|
result = self._cache[entity_id] = {}
|
||||||
|
|
||||||
|
if self._domain is not None and domain in self._domain:
|
||||||
|
result.update(self._domain[domain])
|
||||||
|
|
||||||
|
if self._glob is not None:
|
||||||
|
for pattern, values in self._glob.items():
|
||||||
|
if pattern.match(entity_id):
|
||||||
|
result.update(values)
|
||||||
|
|
||||||
|
if self._exact is not None and entity_id in self._exact:
|
||||||
|
result.update(self._exact[entity_id])
|
||||||
|
|
||||||
|
return result
|
|
@ -1,119 +0,0 @@
|
||||||
"""Test the customize helper."""
|
|
||||||
import homeassistant.helpers.customize as customize
|
|
||||||
from voluptuous import MultipleInvalid
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
class MockHass(object):
|
|
||||||
"""Mock object for HassAssistant."""
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelpersCustomize(object):
|
|
||||||
"""Test homeassistant.helpers.customize module."""
|
|
||||||
|
|
||||||
def setup_method(self, method):
|
|
||||||
"""Setup things to be run when tests are started."""
|
|
||||||
self.entity_id = 'test.test'
|
|
||||||
self.hass = MockHass()
|
|
||||||
|
|
||||||
def _get_overrides(self, overrides):
|
|
||||||
test_domain = 'test.domain'
|
|
||||||
customize.set_customize(self.hass, test_domain, overrides)
|
|
||||||
return customize.get_overrides(self.hass, test_domain, self.entity_id)
|
|
||||||
|
|
||||||
def test_override_single_value(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': [self.entity_id], 'key': 'value'}])
|
|
||||||
|
|
||||||
assert result == {'key': 'value'}
|
|
||||||
|
|
||||||
def test_override_multiple_values(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': [self.entity_id], 'key1': 'value1'},
|
|
||||||
{'entity_id': [self.entity_id], 'key2': 'value2'}])
|
|
||||||
|
|
||||||
assert result == {'key1': 'value1', 'key2': 'value2'}
|
|
||||||
|
|
||||||
def test_override_same_value(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': [self.entity_id], 'key': 'value1'},
|
|
||||||
{'entity_id': [self.entity_id], 'key': 'value2'}])
|
|
||||||
|
|
||||||
assert result == {'key': 'value2'}
|
|
||||||
|
|
||||||
def test_override_by_domain(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': ['test'], 'key': 'value'}])
|
|
||||||
|
|
||||||
assert result == {'key': 'value'}
|
|
||||||
|
|
||||||
def test_override_by_glob(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': ['test.?e*'], 'key': 'value'}])
|
|
||||||
|
|
||||||
assert result == {'key': 'value'}
|
|
||||||
|
|
||||||
def test_override_exact_over_glob_over_domain(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
result = self._get_overrides([
|
|
||||||
{'entity_id': ['test.test'], 'key1': 'valueExact'},
|
|
||||||
{'entity_id': ['test.tes?'],
|
|
||||||
'key1': 'valueGlob',
|
|
||||||
'key2': 'valueGlob'},
|
|
||||||
{'entity_id': ['test'],
|
|
||||||
'key1': 'valueDomain',
|
|
||||||
'key2': 'valueDomain',
|
|
||||||
'key3': 'valueDomain'}])
|
|
||||||
|
|
||||||
assert result == {
|
|
||||||
'key1': 'valueExact',
|
|
||||||
'key2': 'valueGlob',
|
|
||||||
'key3': 'valueDomain'}
|
|
||||||
|
|
||||||
def test_override_deep_dict(self):
|
|
||||||
"""Test we can deep-overwrite a dict."""
|
|
||||||
result = self._get_overrides(
|
|
||||||
[{'entity_id': [self.entity_id],
|
|
||||||
'test': {'key1': 'value1', 'key2': 'value2'}},
|
|
||||||
{'entity_id': [self.entity_id],
|
|
||||||
'test': {'key3': 'value3', 'key2': 'value22'}}])
|
|
||||||
assert result['test'] == {
|
|
||||||
'key1': 'value1',
|
|
||||||
'key2': 'value22',
|
|
||||||
'key3': 'value3'}
|
|
||||||
|
|
||||||
def test_schema_bad_schema(self):
|
|
||||||
"""Test bad customize schemas."""
|
|
||||||
for value in (
|
|
||||||
{'test.test': 10},
|
|
||||||
{'test.test': ['hello']},
|
|
||||||
{'entity_id': {'a': 'b'}},
|
|
||||||
{'entity_id': 10},
|
|
||||||
[{'test.test': 'value'}],
|
|
||||||
):
|
|
||||||
with pytest.raises(
|
|
||||||
MultipleInvalid,
|
|
||||||
message="{} should have raised MultipleInvalid".format(
|
|
||||||
value)):
|
|
||||||
customize.CUSTOMIZE_SCHEMA(value)
|
|
||||||
|
|
||||||
def test_get_customize_schema_allow_extra(self):
|
|
||||||
"""Test schema with ALLOW_EXTRA."""
|
|
||||||
for value in (
|
|
||||||
{'test.test': {'hidden': True}},
|
|
||||||
{'test.test': {'key': ['value1', 'value2']}},
|
|
||||||
[{'entity_id': 'id1', 'key': 'value'}],
|
|
||||||
):
|
|
||||||
customize.CUSTOMIZE_SCHEMA(value)
|
|
||||||
|
|
||||||
def test_get_customize_schema_csv(self):
|
|
||||||
"""Test schema with comma separated entity IDs."""
|
|
||||||
assert [{'entity_id': ['id1', 'id2', 'id3']}] == \
|
|
||||||
customize.CUSTOMIZE_SCHEMA([{'entity_id': 'id1,ID2 , id3'}])
|
|
|
@ -7,8 +7,9 @@ from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import homeassistant.helpers.entity as entity
|
import homeassistant.helpers.entity as entity
|
||||||
from homeassistant.helpers.customize import set_customize
|
|
||||||
from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS
|
from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS
|
||||||
|
from homeassistant.config import DATA_CUSTOMIZE
|
||||||
|
from homeassistant.helpers.entity_values import EntityValues
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
@ -89,10 +90,8 @@ class TestHelpersEntity(object):
|
||||||
|
|
||||||
def test_overwriting_hidden_property_to_true(self):
|
def test_overwriting_hidden_property_to_true(self):
|
||||||
"""Test we can overwrite hidden property to True."""
|
"""Test we can overwrite hidden property to True."""
|
||||||
set_customize(
|
self.hass.data[DATA_CUSTOMIZE] = EntityValues({
|
||||||
self.hass,
|
self.entity.entity_id: {ATTR_HIDDEN: True}})
|
||||||
entity.CORE_DOMAIN,
|
|
||||||
[{'entity_id': [self.entity.entity_id], ATTR_HIDDEN: True}])
|
|
||||||
self.entity.update_ha_state()
|
self.entity.update_ha_state()
|
||||||
|
|
||||||
state = self.hass.states.get(self.entity.entity_id)
|
state = self.hass.states.get(self.entity.entity_id)
|
||||||
|
|
68
tests/helpers/test_entity_values.py
Normal file
68
tests/helpers/test_entity_values.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
"""Test the entity values helper."""
|
||||||
|
from collections import OrderedDict
|
||||||
|
from homeassistant.helpers.entity_values import EntityValues as EV
|
||||||
|
|
||||||
|
ent = 'test.test'
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_single_value():
|
||||||
|
"""Test values with exact match."""
|
||||||
|
store = EV({ent: {'key': 'value'}})
|
||||||
|
assert store.get(ent) == {'key': 'value'}
|
||||||
|
assert len(store._cache) == 1
|
||||||
|
assert store.get(ent) == {'key': 'value'}
|
||||||
|
assert len(store._cache) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_by_domain():
|
||||||
|
"""Test values with domain match."""
|
||||||
|
store = EV(domain={'test': {'key': 'value'}})
|
||||||
|
assert store.get(ent) == {'key': 'value'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_by_glob():
|
||||||
|
"""Test values with glob match."""
|
||||||
|
store = EV(glob={'test.?e*': {'key': 'value'}})
|
||||||
|
assert store.get(ent) == {'key': 'value'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_glob_overrules_domain():
|
||||||
|
"""Test domain overrules glob match."""
|
||||||
|
store = EV(
|
||||||
|
domain={'test': {'key': 'domain'}},
|
||||||
|
glob={'test.?e*': {'key': 'glob'}})
|
||||||
|
assert store.get(ent) == {'key': 'glob'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_exact_overrules_domain():
|
||||||
|
"""Test exact overrules domain match."""
|
||||||
|
store = EV(
|
||||||
|
exact={'test.test': {'key': 'exact'}},
|
||||||
|
domain={'test': {'key': 'domain'}},
|
||||||
|
glob={'test.?e*': {'key': 'glob'}})
|
||||||
|
assert store.get(ent) == {'key': 'exact'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_merging_values():
|
||||||
|
"""Test merging glob, domain and exact configs."""
|
||||||
|
store = EV(
|
||||||
|
exact={'test.test': {'exact_key': 'exact'}},
|
||||||
|
domain={'test': {'domain_key': 'domain'}},
|
||||||
|
glob={'test.?e*': {'glob_key': 'glob'}})
|
||||||
|
assert store.get(ent) == {
|
||||||
|
'exact_key': 'exact',
|
||||||
|
'domain_key': 'domain',
|
||||||
|
'glob_key': 'glob',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_glob_order():
|
||||||
|
"""Test merging glob, domain and exact configs."""
|
||||||
|
glob = OrderedDict()
|
||||||
|
glob['test.*est'] = {"value": "first"}
|
||||||
|
glob['test.*'] = {"value": "second"}
|
||||||
|
|
||||||
|
store = EV(glob=glob)
|
||||||
|
assert store.get(ent) == {
|
||||||
|
'value': 'second'
|
||||||
|
}
|
|
@ -547,11 +547,5 @@ def test_merge_customize(hass):
|
||||||
}
|
}
|
||||||
yield from config_util.async_process_ha_core_config(hass, core_config)
|
yield from config_util.async_process_ha_core_config(hass, core_config)
|
||||||
|
|
||||||
entity = Entity()
|
assert hass.data[config_util.DATA_CUSTOMIZE].get('b.b') == \
|
||||||
entity.entity_id = 'b.b'
|
{'friendly_name': 'BB'}
|
||||||
entity.hass = hass
|
|
||||||
yield from entity.async_update_ha_state()
|
|
||||||
|
|
||||||
state = hass.states.get('b.b')
|
|
||||||
assert state is not None
|
|
||||||
assert state.attributes['friendly_name'] == 'BB'
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue