ps - add reload core config service (#2350)

This commit is contained in:
Paulus Schoutsen 2016-06-22 09:13:18 -07:00 committed by GitHub
parent 9ce9b8debb
commit a70f922a71
6 changed files with 156 additions and 54 deletions

View File

@ -25,8 +25,8 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.entity import Entity
event_decorators, service, config_per_platform, extract_domain_configs,
entity)
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
@ -412,8 +412,7 @@ def process_ha_core_config(hass, config):
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
for entity_id, attrs in config.get(CONF_CUSTOMIZE).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
entity.set_customize(config.get(CONF_CUSTOMIZE))
if CONF_TEMPERATURE_UNIT in config:
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]

View File

@ -19,6 +19,8 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
def is_on(hass, entity_id=None):
"""Load up the module to call the is_on method.
@ -73,6 +75,11 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
def setup(hass, config):
"""Setup general services related to Home Assistant."""
def handle_turn_service(service):
@ -111,4 +118,21 @@ def setup(hass, config):
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config, bootstrap
try:
path = config.find_config_file(hass.config.config_dir)
conf = config.load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error(err)
return
bootstrap.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)
return True

View File

@ -149,9 +149,9 @@ def load_yaml_config_file(config_path):
conf_dict = load_yaml(config_path)
if not isinstance(conf_dict, dict):
_LOGGER.error(
'The configuration file %s does not contain a dictionary',
msg = 'The configuration file {} does not contain a dictionary'.format(
os.path.basename(config_path))
raise HomeAssistantError()
_LOGGER.error(msg)
raise HomeAssistantError(msg)
return conf_dict

View File

@ -1,6 +1,6 @@
"""An abstract class for entities."""
import logging
import re
from collections import defaultdict
from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON,
@ -10,8 +10,10 @@ from homeassistant.const import (
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify
# Dict mapping entity_id to a boolean that overwrites the hidden property
_OVERWRITE = defaultdict(dict)
# Entity attributes that we will overwrite
_OVERWRITE = {}
_LOGGER = logging.getLogger(__name__)
# Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
@ -22,7 +24,7 @@ def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
name = (name or DEVICE_DEFAULT_NAME).lower()
if current_ids is None:
if hass is None:
raise RuntimeError("Missing required parameter currentids or hass")
raise ValueError("Missing required parameter currentids or hass")
current_ids = hass.states.entity_ids()
@ -30,6 +32,13 @@ def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
entity_id_format.format(slugify(name)), current_ids)
def set_customize(customize):
"""Overwrite all current customize settings."""
global _OVERWRITE
_OVERWRITE = {key.lower(): val for key, val in customize.items()}
def split_entity_id(entity_id):
"""Split a state entity_id into domain, object_id."""
return entity_id.split(".", 1)
@ -207,20 +216,6 @@ class Entity(object):
"""Return the representation."""
return "<Entity {}: {}>".format(self.name, self.state)
@staticmethod
def overwrite_attribute(entity_id, attrs, vals):
"""Overwrite any attribute of an entity.
This function should receive a list of attributes and a
list of values. Set attribute to None to remove any overwritten
value in place.
"""
for attr, val in zip(attrs, vals):
if val is None:
_OVERWRITE[entity_id.lower()].pop(attr, None)
else:
_OVERWRITE[entity_id.lower()][attr] = val
class ToggleEntity(Entity):
"""An abstract class for entities that can be turned on and off."""
@ -238,11 +233,13 @@ class ToggleEntity(Entity):
def turn_on(self, **kwargs):
"""Turn the entity on."""
pass
_LOGGER.warning('Method turn_on not implemented for %s',
self.entity_id)
def turn_off(self, **kwargs):
"""Turn the entity off."""
pass
_LOGGER.warning('Method turn_off not implemented for %s',
self.entity_id)
def toggle(self, **kwargs):
"""Toggle the entity off."""

View File

@ -2,13 +2,18 @@
# pylint: disable=protected-access,too-many-public-methods
import unittest
from unittest.mock import patch
from tempfile import TemporaryDirectory
import yaml
import homeassistant.core as ha
from homeassistant import config
from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
import homeassistant.components as comps
from homeassistant.helpers import entity
from tests.common import get_test_home_assistant
from tests.common import get_test_home_assistant, mock_service
class TestComponentsCore(unittest.TestCase):
@ -31,47 +36,40 @@ class TestComponentsCore(unittest.TestCase):
self.assertTrue(comps.is_on(self.hass, 'light.Bowl'))
self.assertFalse(comps.is_on(self.hass, 'light.Ceiling'))
self.assertTrue(comps.is_on(self.hass))
self.assertFalse(comps.is_on(self.hass, 'non_existing.entity'))
def test_turn_on_without_entities(self):
"""Test turn_on method without entities."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
comps.turn_on(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(0, len(calls))
def test_turn_on(self):
"""Test turn_on method."""
runs = []
self.hass.services.register(
'light', SERVICE_TURN_ON, lambda x: runs.append(1))
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
comps.turn_on(self.hass, 'light.Ceiling')
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(1, len(calls))
def test_turn_off(self):
"""Test turn_off method."""
runs = []
self.hass.services.register(
'light', SERVICE_TURN_OFF, lambda x: runs.append(1))
calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF)
comps.turn_off(self.hass, 'light.Bowl')
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(1, len(calls))
def test_toggle(self):
"""Test toggle method."""
runs = []
self.hass.services.register(
'light', SERVICE_TOGGLE, lambda x: runs.append(1))
calls = mock_service(self.hass, 'light', SERVICE_TOGGLE)
comps.toggle(self.hass, 'light.Bowl')
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(1, len(calls))
@patch('homeassistant.core.ServiceRegistry.call')
def test_turn_on_to_not_block_for_domains_without_service(self, mock_call):
"""Test if turn_on is blocking domain with no service."""
self.hass.services.register('light', SERVICE_TURN_ON, lambda x: x)
mock_service(self.hass, 'light', SERVICE_TURN_ON)
# We can't test if our service call results in services being called
# because by mocking out the call service method, we mock out all
@ -89,3 +87,62 @@ class TestComponentsCore(unittest.TestCase):
self.assertEqual(
('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False),
mock_call.call_args_list[1][0])
def test_reload_core_conf(self):
"""Test reload core conf service."""
ent = entity.Entity()
ent.entity_id = 'test.entity'
ent.hass = self.hass
ent.update_ha_state()
state = self.hass.states.get('test.entity')
assert state is not None
assert state.state == 'unknown'
assert state.attributes == {}
with TemporaryDirectory() as conf_dir:
self.hass.config.config_dir = conf_dir
conf_yaml = self.hass.config.path(config.YAML_CONFIG_FILE)
with open(conf_yaml, 'a') as fp:
fp.write(yaml.dump({
ha.DOMAIN: {
'latitude': 10,
'longitude': 20,
'customize': {
'test.Entity': {
'hello': 'world'
}
}
}
}))
comps.reload_core_config(self.hass)
self.hass.pool.block_till_done()
assert 10 == self.hass.config.latitude
assert 20 == self.hass.config.longitude
ent.update_ha_state()
state = self.hass.states.get('test.entity')
assert state is not None
assert state.state == 'unknown'
assert state.attributes.get('hello') == 'world'
@patch('homeassistant.components._LOGGER.error')
@patch('homeassistant.bootstrap.process_ha_core_config')
def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
"""Test reload core conf service."""
with TemporaryDirectory() as conf_dir:
self.hass.config.config_dir = conf_dir
conf_yaml = self.hass.config.path(config.YAML_CONFIG_FILE)
with open(conf_yaml, 'a') as fp:
fp.write(yaml.dump(['invalid', 'config']))
comps.reload_core_config(self.hass)
self.hass.pool.block_till_done()
assert mock_error.called
assert mock_process.called is False

View File

@ -21,8 +21,7 @@ class TestHelpersEntity(unittest.TestCase):
def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
self.hass.stop()
entity.Entity.overwrite_attribute(self.entity.entity_id,
[ATTR_HIDDEN], [None])
entity.set_customize({})
def test_default_hidden_not_in_attributes(self):
"""Test that the default hidden property is set to False."""
@ -32,8 +31,7 @@ class TestHelpersEntity(unittest.TestCase):
def test_overwriting_hidden_property_to_true(self):
"""Test we can overwrite hidden property to True."""
entity.Entity.overwrite_attribute(self.entity.entity_id,
[ATTR_HIDDEN], [True])
entity.set_customize({self.entity.entity_id: {ATTR_HIDDEN: True}})
self.entity.update_ha_state()
state = self.hass.states.get(self.entity.entity_id)
@ -43,3 +41,30 @@ class TestHelpersEntity(unittest.TestCase):
"""Test split_entity_id."""
self.assertEqual(['domain', 'object_id'],
entity.split_entity_id('domain.object_id'))
def test_generate_entity_id_requires_hass_or_ids(self):
"""Ensure we require at least hass or current ids."""
fmt = 'test.{}'
with self.assertRaises(ValueError):
entity.generate_entity_id(fmt, 'hello world')
def test_generate_entity_id_given_hass(self):
"""Test generating an entity id given hass object."""
fmt = 'test.{}'
self.assertEqual(
'test.overwrite_hidden_true_2',
entity.generate_entity_id(fmt, 'overwrite hidden true',
hass=self.hass))
def test_generate_entity_id_given_keys(self):
"""Test generating an entity id given current ids."""
fmt = 'test.{}'
self.assertEqual(
'test.overwrite_hidden_true_2',
entity.generate_entity_id(
fmt, 'overwrite hidden true',
current_ids=['test.overwrite_hidden_true']))
self.assertEqual(
'test.overwrite_hidden_true',
entity.generate_entity_id(fmt, 'overwrite hidden true',
current_ids=['test.another_entity']))