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

View file

@ -19,6 +19,8 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
"""Load up the module to call the is_on method. """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) 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): def setup(hass, config):
"""Setup general services related to Home Assistant.""" """Setup general services related to Home Assistant."""
def handle_turn_service(service): 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_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, 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 return True

View file

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

View file

@ -1,6 +1,6 @@
"""An abstract class for entities.""" """An abstract class for entities."""
import logging
import re import re
from collections import defaultdict
from homeassistant.const import ( from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON, 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.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify from homeassistant.util import ensure_unique_string, slugify
# Dict mapping entity_id to a boolean that overwrites the hidden property # Entity attributes that we will overwrite
_OVERWRITE = defaultdict(dict) _OVERWRITE = {}
_LOGGER = logging.getLogger(__name__)
# Pattern for validating entity IDs (format: <domain>.<entity>) # Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$") 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() name = (name or DEVICE_DEFAULT_NAME).lower()
if current_ids is None: if current_ids is None:
if hass 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() 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) 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): def split_entity_id(entity_id):
"""Split a state entity_id into domain, object_id.""" """Split a state entity_id into domain, object_id."""
return entity_id.split(".", 1) return entity_id.split(".", 1)
@ -207,20 +216,6 @@ class Entity(object):
"""Return the representation.""" """Return the representation."""
return "<Entity {}: {}>".format(self.name, self.state) 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): class ToggleEntity(Entity):
"""An abstract class for entities that can be turned on and off.""" """An abstract class for entities that can be turned on and off."""
@ -238,11 +233,13 @@ class ToggleEntity(Entity):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the entity on.""" """Turn the entity on."""
pass _LOGGER.warning('Method turn_on not implemented for %s',
self.entity_id)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the entity off.""" """Turn the entity off."""
pass _LOGGER.warning('Method turn_off not implemented for %s',
self.entity_id)
def toggle(self, **kwargs): def toggle(self, **kwargs):
"""Toggle the entity off.""" """Toggle the entity off."""

View file

@ -2,13 +2,18 @@
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from tempfile import TemporaryDirectory
import yaml
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant import config
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
import homeassistant.components as comps 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): class TestComponentsCore(unittest.TestCase):
@ -31,47 +36,40 @@ class TestComponentsCore(unittest.TestCase):
self.assertTrue(comps.is_on(self.hass, 'light.Bowl')) self.assertTrue(comps.is_on(self.hass, 'light.Bowl'))
self.assertFalse(comps.is_on(self.hass, 'light.Ceiling')) self.assertFalse(comps.is_on(self.hass, 'light.Ceiling'))
self.assertTrue(comps.is_on(self.hass)) 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): def test_turn_on(self):
"""Test turn_on method.""" """Test turn_on method."""
runs = [] calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.services.register(
'light', SERVICE_TURN_ON, lambda x: runs.append(1))
comps.turn_on(self.hass, 'light.Ceiling') comps.turn_on(self.hass, 'light.Ceiling')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))
self.assertEqual(1, len(runs))
def test_turn_off(self): def test_turn_off(self):
"""Test turn_off method.""" """Test turn_off method."""
runs = [] calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF)
self.hass.services.register(
'light', SERVICE_TURN_OFF, lambda x: runs.append(1))
comps.turn_off(self.hass, 'light.Bowl') comps.turn_off(self.hass, 'light.Bowl')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))
self.assertEqual(1, len(runs))
def test_toggle(self): def test_toggle(self):
"""Test toggle method.""" """Test toggle method."""
runs = [] calls = mock_service(self.hass, 'light', SERVICE_TOGGLE)
self.hass.services.register(
'light', SERVICE_TOGGLE, lambda x: runs.append(1))
comps.toggle(self.hass, 'light.Bowl') comps.toggle(self.hass, 'light.Bowl')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))
self.assertEqual(1, len(runs))
@patch('homeassistant.core.ServiceRegistry.call') @patch('homeassistant.core.ServiceRegistry.call')
def test_turn_on_to_not_block_for_domains_without_service(self, mock_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.""" """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 # 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 # because by mocking out the call service method, we mock out all
@ -89,3 +87,62 @@ class TestComponentsCore(unittest.TestCase):
self.assertEqual( self.assertEqual(
('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False), ('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False),
mock_call.call_args_list[1][0]) 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 def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
entity.Entity.overwrite_attribute(self.entity.entity_id, entity.set_customize({})
[ATTR_HIDDEN], [None])
def test_default_hidden_not_in_attributes(self): def test_default_hidden_not_in_attributes(self):
"""Test that the default hidden property is set to False.""" """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): def test_overwriting_hidden_property_to_true(self):
"""Test we can overwrite hidden property to True.""" """Test we can overwrite hidden property to True."""
entity.Entity.overwrite_attribute(self.entity.entity_id, entity.set_customize({self.entity.entity_id: {ATTR_HIDDEN: True}})
[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)
@ -43,3 +41,30 @@ class TestHelpersEntity(unittest.TestCase):
"""Test split_entity_id.""" """Test split_entity_id."""
self.assertEqual(['domain', 'object_id'], self.assertEqual(['domain', 'object_id'],
entity.split_entity_id('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']))