From 9c257498801e20290cf3da75ce1ef6aabbe8054b Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2019 22:51:10 +0200 Subject: [PATCH 1/5] Fire event when core config is updated --- homeassistant/config.py | 45 +---------------------- homeassistant/core.py | 79 +++++++++++++++++++++++++++++++++++++---- tests/test_config.py | 10 ++++-- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 88abf2ac791b67..22bb4072f700d3 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -50,9 +50,6 @@ ('ios.conf', '.ios.conf'), ) -CORE_STORAGE_KEY = 'homeassistant.core_config' -CORE_STORAGE_VERSION = 1 - SOURCE_DISCOVERED = 'discovered' SOURCE_STORAGE = 'storage' SOURCE_YAML = 'yaml' @@ -494,28 +491,6 @@ def _set_time_zone(hass: HomeAssistant, time_zone_str: Optional[str]) -> None: _LOGGER.error("Received invalid time zone %s", time_zone_str) -async def async_load_ha_core_config(hass: HomeAssistant) -> None: - """Store [homeassistant] core config.""" - store = hass.helpers.storage.Store(CORE_STORAGE_VERSION, CORE_STORAGE_KEY, - private=True) - data = await store.async_load() - if not data: - return - - hac = hass.config - hac.config_source = SOURCE_STORAGE - hac.latitude = data['latitude'] - hac.longitude = data['longitude'] - hac.elevation = data['elevation'] - unit_system = data['unit_system'] - if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: - hac.units = IMPERIAL_SYSTEM - else: - hac.units = METRIC_SYSTEM - hac.location_name = data['location_name'] - _set_time_zone(hass, data['time_zone']) - - async def async_process_ha_core_config( hass: HomeAssistant, config: Dict, api_password: Optional[str] = None, @@ -554,7 +529,7 @@ async def async_process_ha_core_config( auth_conf, mfa_conf)) - await async_load_ha_core_config(hass) + await hass.config.load() hac = hass.config @@ -668,24 +643,6 @@ async def async_process_ha_core_config( ", ".join('{}: {}'.format(key, val) for key, val in discovered)) -async def async_store_ha_core_config(hass: HomeAssistant) -> None: - """Store [homeassistant] core config.""" - config = hass.config.as_dict() - - data = { - 'latitude': config['latitude'], - 'longitude': config['longitude'], - 'elevation': config['elevation'], - 'unit_system': hass.config.units.name, - 'location_name': config['location_name'], - 'time_zone': config['time_zone'], - } - - store = hass.helpers.storage.Store(CORE_STORAGE_VERSION, CORE_STORAGE_KEY, - private=True) - await store.async_save(data) - - def _log_pkg_error( package: str, component: str, config: Dict, message: str) -> None: """Log an error while merging packages.""" diff --git a/homeassistant/core.py b/homeassistant/core.py index a02c1b687ab625..eab3c33228cc51 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -28,8 +28,8 @@ from homeassistant.const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, - ATTR_SERVICE_DATA, ATTR_SECONDS, EVENT_CALL_SERVICE, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + ATTR_SERVICE_DATA, ATTR_SECONDS, CONF_UNIT_SYSTEM_IMPERIAL, + EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__) @@ -43,7 +43,8 @@ from homeassistant import util import homeassistant.util.dt as dt_util from homeassistant.util import location, slugify -from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA +from homeassistant.util.unit_system import ( # NOQA + UnitSystem, IMPERIAL_SYSTEM, METRIC_SYSTEM) # Typing imports that create a circular dependency # pylint: disable=using-constant-test @@ -56,8 +57,13 @@ CALLBACK_TYPE = Callable[[], None] # pylint: enable=invalid-name +CORE_STORAGE_KEY = 'homeassistant.core_config' +CORE_STORAGE_VERSION = 1 + DOMAIN = 'homeassistant' +EVENT_CORE_CONFIG_UPDATE = 'core_config_updated' + # How long we wait for the result of a service call SERVICE_CALL_LIMIT = 10 # seconds @@ -144,7 +150,7 @@ def __init__( self.bus = EventBus(self) self.services = ServiceRegistry(self) self.states = StateMachine(self.bus, self.loop) - self.config = Config() # type: Config + self.config = Config(self) # type: Config self.components = loader.Components(self) self.helpers = loader.Helpers(self) # This is a dictionary that any component can store any data on. @@ -1168,8 +1174,10 @@ async def _execute_service(self, handler: Service, class Config: """Configuration settings for Home Assistant.""" - def __init__(self) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize a new config object.""" + self.hass = hass + self.latitude = None # type: Optional[float] self.longitude = None # type: Optional[float] self.elevation = None # type: Optional[int] @@ -1235,7 +1243,7 @@ def is_allowed_path(self, path: str) -> bool: return False def as_dict(self) -> Dict: - """Create a dictionary representation of this dict. + """Create a dictionary representation of the configuration. Async friendly. """ @@ -1257,6 +1265,65 @@ def as_dict(self) -> Dict: 'config_source': self.config_source } + def _update(self, values: Dict, source: str) -> None: + """Update the configuration from a dictionary. + + Async friendly. + """ + from homeassistant.config import _set_time_zone + self.config_source = source + self.latitude = values['latitude'] + self.longitude = values['longitude'] + self.elevation = values['elevation'] + unit_system = values['unit_system'] + if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: + self.units = IMPERIAL_SYSTEM + else: + self.units = METRIC_SYSTEM + self.location_name = values['location_name'] + _set_time_zone(self.hass, values['time_zone']) + + async def update(self, values: Dict) -> None: + """Update the configuration from a dictionary. + + Async friendly. + """ + from homeassistant.config import SOURCE_STORAGE + self._update(values, SOURCE_STORAGE) + self.store() + self.hass.bus.async_fire( + EVENT_CORE_CONFIG_UPDATE, + values + ) + + async def load(self) -> None: + """Load [homeassistant] core config.""" + from homeassistant.config import SOURCE_STORAGE + store = self.hass.helpers.storage.Store( + CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True) + data = await store.async_load() + if not data: + return + + self._update(data, SOURCE_STORAGE) + + async def store(self) -> None: + """Store [homeassistant] core config.""" + config = self.as_dict() + + data = { + 'latitude': config['latitude'], + 'longitude': config['longitude'], + 'elevation': config['elevation'], + 'unit_system': self.units.name, + 'location_name': config['location_name'], + 'time_zone': config['time_zone'], + } + + store = self.hass.helpers.storage.Store( + CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True) + await store.async_save(data) + def _async_create_timer(hass: HomeAssistant) -> None: """Create a timer that will start on HOMEASSISTANT_START.""" diff --git a/tests/test_config.py b/tests/test_config.py index c081d97ed7c909..d2dacae9f7263e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -475,6 +475,8 @@ async def test_override_stored_configuration(hass, hass_storage): async def test_loading_configuration(hass): """Test loading core config onto hass object.""" hass.config = mock.Mock() + hass.config.load.return_value = asyncio.Future() + hass.config.load.return_value.set_result(None) await config_util.async_process_ha_core_config(hass, { 'latitude': 60, @@ -500,6 +502,8 @@ async def test_loading_configuration(hass): async def test_loading_configuration_temperature_unit(hass): """Test backward compatibility when loading core config.""" hass.config = mock.Mock() + hass.config.load.return_value = asyncio.Future() + hass.config.load.return_value.set_result(None) await config_util.async_process_ha_core_config(hass, { 'latitude': 60, @@ -522,6 +526,8 @@ async def test_loading_configuration_temperature_unit(hass): async def test_loading_configuration_from_packages(hass): """Test loading packages config onto hass object config.""" hass.config = mock.Mock() + hass.config.load.return_value = asyncio.Future() + hass.config.load.return_value.set_result(None) await config_util.async_process_ha_core_config(hass, { 'latitude': 39, @@ -586,12 +592,12 @@ async def test_discovering_configuration_auto_detect_fails(mock_detect, mock_elevation, hass): """Test config remains unchanged if discovery fails.""" - hass.config = Config() + hass.config = Config(hass) hass.config.config_dir = "/test/config" await config_util.async_process_ha_core_config(hass, {}) - blankConfig = Config() + blankConfig = Config(hass) assert hass.config.latitude == blankConfig.latitude assert hass.config.longitude == blankConfig.longitude assert hass.config.elevation == blankConfig.elevation From 8191360cf61c8a1c789ee62a3a6db7937e24a3e3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 19 May 2019 17:52:10 +0200 Subject: [PATCH 2/5] Review comments, fix tests --- homeassistant/config.py | 20 ++-------- homeassistant/core.py | 85 +++++++++++++++++++++++++++++------------ tests/test_config.py | 8 ---- tests/test_core.py | 8 +++- 4 files changed, 70 insertions(+), 51 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 22bb4072f700d3..05a5cf8ed39d4a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -30,7 +30,7 @@ ) from homeassistant.util.yaml import load_yaml, SECRET_YAML import homeassistant.helpers.config_validation as cv -from homeassistant.util import dt as date_util, location as loc_util +from homeassistant.util import location as loc_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM from homeassistant.helpers.entity_values import EntityValues from homeassistant.helpers import config_per_platform, extract_domain_configs @@ -477,20 +477,6 @@ def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str: return message -def _set_time_zone(hass: HomeAssistant, time_zone_str: Optional[str]) -> None: - """Help to set the time zone.""" - if time_zone_str is None: - return - - time_zone = date_util.get_time_zone(time_zone_str) - - if time_zone: - hass.config.time_zone = time_zone - date_util.set_default_time_zone(time_zone) - else: - _LOGGER.error("Received invalid time zone %s", time_zone_str) - - async def async_process_ha_core_config( hass: HomeAssistant, config: Dict, api_password: Optional[str] = None, @@ -545,7 +531,7 @@ async def async_process_ha_core_config( if key in config: setattr(hac, attr, config[key]) - _set_time_zone(hass, config.get(CONF_TIME_ZONE)) + hac.set_time_zone(config.get(CONF_TIME_ZONE)) # Init whitelist external dir hac.whitelist_external_dirs = {hass.config.path('www')} @@ -626,7 +612,7 @@ async def async_process_ha_core_config( discovered.append(('name', info.city)) if hac.time_zone is None: - _set_time_zone(hass, info.time_zone) + hac.set_time_zone(info.time_zone) discovered.append(('time_zone', info.time_zone)) if hac.elevation is None and hac.latitude is not None and \ diff --git a/homeassistant/core.py b/homeassistant/core.py index eab3c33228cc51..ac9826ebd392c2 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1265,35 +1265,66 @@ def as_dict(self) -> Dict: 'config_source': self.config_source } - def _update(self, values: Dict, source: str) -> None: + def set_time_zone(self, time_zone_str: Optional[str]) -> None: + """Help to set the time zone.""" + if time_zone_str is None: + return + + time_zone = dt_util.get_time_zone(time_zone_str) + + if time_zone: + self.time_zone = time_zone + dt_util.set_default_time_zone(time_zone) + else: + _LOGGER.error("Received invalid time zone %s", time_zone_str) + + @callback + def _update(self, + source: str, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + elevation: Optional[int] = None, + unit_system: Optional[str] = None, + location_name: Optional[str] = None, + time_zone: Optional[str] = None) -> None: """Update the configuration from a dictionary. Async friendly. """ - from homeassistant.config import _set_time_zone self.config_source = source - self.latitude = values['latitude'] - self.longitude = values['longitude'] - self.elevation = values['elevation'] - unit_system = values['unit_system'] - if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: - self.units = IMPERIAL_SYSTEM - else: - self.units = METRIC_SYSTEM - self.location_name = values['location_name'] - _set_time_zone(self.hass, values['time_zone']) - - async def update(self, values: Dict) -> None: + if latitude is not None: + self.latitude = latitude + if longitude is not None: + self.longitude = longitude + if elevation is not None: + self.elevation = elevation + if unit_system is not None: + if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: + self.units = IMPERIAL_SYSTEM + else: + self.units = METRIC_SYSTEM + if location_name is not None: + self.location_name = location_name + if time_zone is not None: + self.set_time_zone(time_zone) + + async def update(self, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + elevation: Optional[int] = None, + unit_system: Optional[str] = None, + location_name: Optional[str] = None, + time_zone: Optional[str] = None) -> None: """Update the configuration from a dictionary. Async friendly. """ from homeassistant.config import SOURCE_STORAGE - self._update(values, SOURCE_STORAGE) - self.store() + self._update(SOURCE_STORAGE, latitude, longitude, elevation, + unit_system, location_name, time_zone) + await self.store() self.hass.bus.async_fire( - EVENT_CORE_CONFIG_UPDATE, - values + EVENT_CORE_CONFIG_UPDATE ) async def load(self) -> None: @@ -1305,19 +1336,23 @@ async def load(self) -> None: if not data: return - self._update(data, SOURCE_STORAGE) + self._update(SOURCE_STORAGE, data['latitude'], data['longitude'], + data['elevation'], data['unit_system'], + data['location_name'], data['time_zone']) async def store(self) -> None: """Store [homeassistant] core config.""" - config = self.as_dict() + time_zone = dt_util.UTC.zone + if self.time_zone and getattr(self.time_zone, 'zone'): + time_zone = getattr(self.time_zone, 'zone') data = { - 'latitude': config['latitude'], - 'longitude': config['longitude'], - 'elevation': config['elevation'], + 'latitude': self.latitude, + 'longitude': self.longitude, + 'elevation': self.elevation, 'unit_system': self.units.name, - 'location_name': config['location_name'], - 'time_zone': config['time_zone'], + 'location_name': self.location_name, + 'time_zone': time_zone, } store = self.hass.helpers.storage.Store( diff --git a/tests/test_config.py b/tests/test_config.py index d2dacae9f7263e..4fd43133093099 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -474,10 +474,6 @@ async def test_override_stored_configuration(hass, hass_storage): async def test_loading_configuration(hass): """Test loading core config onto hass object.""" - hass.config = mock.Mock() - hass.config.load.return_value = asyncio.Future() - hass.config.load.return_value.set_result(None) - await config_util.async_process_ha_core_config(hass, { 'latitude': 60, 'longitude': 50, @@ -501,10 +497,6 @@ async def test_loading_configuration(hass): async def test_loading_configuration_temperature_unit(hass): """Test backward compatibility when loading core config.""" - hass.config = mock.Mock() - hass.config.load.return_value = asyncio.Future() - hass.config.load.return_value.set_result(None) - await config_util.async_process_ha_core_config(hass, { 'latitude': 60, 'longitude': 50, diff --git a/tests/test_core.py b/tests/test_core.py index afbbe3e33b254b..caa411d59b2120 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -871,9 +871,15 @@ class TestConfig(unittest.TestCase): # pylint: disable=invalid-name def setUp(self): """Set up things to be run when tests are started.""" - self.config = ha.Config() + self.hass = get_test_home_assistant() + self.config = ha.Config(self.hass) assert self.config.config_dir is None + # pylint: disable=invalid-name + def tearDown(self): + """Stop down stuff we started.""" + self.hass.stop() + def test_path_with_file(self): """Test get_config_path method.""" self.config.config_dir = '/tmp/ha-config' From 1e065483d4a17bce5ed69a445a870b64760ef71b Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 19 May 2019 21:40:25 +0200 Subject: [PATCH 3/5] Review comments, add tests --- homeassistant/config.py | 11 +++++----- homeassistant/const.py | 18 +++++++++------- homeassistant/core.py | 48 ++++++++++++++++------------------------- tests/test_config.py | 30 ++++++++++++++++++++++++-- tests/test_core.py | 29 ++++++++++++++++++++++++- 5 files changed, 90 insertions(+), 46 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 05a5cf8ed39d4a..18266ec0efbcd4 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -23,7 +23,9 @@ __version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB, CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_AUTH_MFA_MODULES, CONF_TYPE, CONF_ID) -from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant +from homeassistant.core import ( + DOMAIN as CONF_CORE, SOURCE_DISCOVERED, SOURCE_YAML, HomeAssistant, + callback) from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import ( Integration, async_get_integration, IntegrationNotFound @@ -50,10 +52,6 @@ ('ios.conf', '.ios.conf'), ) -SOURCE_DISCOVERED = 'discovered' -SOURCE_STORAGE = 'storage' -SOURCE_YAML = 'yaml' - DEFAULT_CORE_CONFIG = ( # Tuples (attribute, default, auto detect property, description) (CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is ' @@ -531,7 +529,8 @@ async def async_process_ha_core_config( if key in config: setattr(hac, attr, config[key]) - hac.set_time_zone(config.get(CONF_TIME_ZONE)) + if CONF_TIME_ZONE in config: + hac.set_time_zone(config[CONF_TIME_ZONE]) # Init whitelist external dir hac.whitelist_external_dirs = {hass.config.path('www')} diff --git a/homeassistant/const.py b/homeassistant/const.py index 8d21c9d191e7f2..1dcea3e2dafb4f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -160,21 +160,23 @@ CONF_ZONE = 'zone' # #### EVENTS #### +EVENT_AUTOMATION_TRIGGERED = 'automation_triggered' +EVENT_CALL_SERVICE = 'call_service' +EVENT_COMPONENT_LOADED = 'component_loaded' +EVENT_CORE_CONFIG_UPDATE = 'core_config_updated' +EVENT_HOMEASSISTANT_CLOSE = 'homeassistant_close' EVENT_HOMEASSISTANT_START = 'homeassistant_start' EVENT_HOMEASSISTANT_STOP = 'homeassistant_stop' -EVENT_HOMEASSISTANT_CLOSE = 'homeassistant_close' -EVENT_STATE_CHANGED = 'state_changed' -EVENT_TIME_CHANGED = 'time_changed' -EVENT_CALL_SERVICE = 'call_service' +EVENT_LOGBOOK_ENTRY = 'logbook_entry' EVENT_PLATFORM_DISCOVERED = 'platform_discovered' -EVENT_COMPONENT_LOADED = 'component_loaded' +EVENT_SCRIPT_STARTED = 'script_started' EVENT_SERVICE_REGISTERED = 'service_registered' EVENT_SERVICE_REMOVED = 'service_removed' -EVENT_LOGBOOK_ENTRY = 'logbook_entry' +EVENT_STATE_CHANGED = 'state_changed' EVENT_THEMES_UPDATED = 'themes_updated' EVENT_TIMER_OUT_OF_SYNC = 'timer_out_of_sync' -EVENT_AUTOMATION_TRIGGERED = 'automation_triggered' -EVENT_SCRIPT_STARTED = 'script_started' +EVENT_TIME_CHANGED = 'time_changed' + # #### DEVICE CLASSES #### DEVICE_CLASS_BATTERY = 'battery' diff --git a/homeassistant/core.py b/homeassistant/core.py index ac9826ebd392c2..6324d8701e2713 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -27,12 +27,12 @@ import voluptuous as vol from homeassistant.const import ( - ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, - ATTR_SERVICE_DATA, ATTR_SECONDS, CONF_UNIT_SYSTEM_IMPERIAL, - EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED, - EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__) + ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, ATTR_SERVICE_DATA, + ATTR_SECONDS, CONF_UNIT_SYSTEM_IMPERIAL, EVENT_CALL_SERVICE, + EVENT_CORE_CONFIG_UPDATE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED, + EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, + EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__) from homeassistant import loader from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError, InvalidStateError, @@ -62,11 +62,14 @@ DOMAIN = 'homeassistant' -EVENT_CORE_CONFIG_UPDATE = 'core_config_updated' - # How long we wait for the result of a service call SERVICE_CALL_LIMIT = 10 # seconds +# Source of core configuration +SOURCE_DISCOVERED = 'discovered' +SOURCE_STORAGE = 'storage' +SOURCE_YAML = 'yaml' + # How long to wait till things that run on startup have to finish. TIMEOUT_EVENT_START = 15 @@ -1265,21 +1268,19 @@ def as_dict(self) -> Dict: 'config_source': self.config_source } - def set_time_zone(self, time_zone_str: Optional[str]) -> None: + def set_time_zone(self, time_zone_str: str) -> None: """Help to set the time zone.""" - if time_zone_str is None: - return - time_zone = dt_util.get_time_zone(time_zone_str) if time_zone: self.time_zone = time_zone dt_util.set_default_time_zone(time_zone) else: - _LOGGER.error("Received invalid time zone %s", time_zone_str) + raise ValueError( + "Received invalid time zone {}".format(time_zone_str)) @callback - def _update(self, + def _update(self, *, source: str, latitude: Optional[float] = None, longitude: Optional[float] = None, @@ -1308,37 +1309,26 @@ def _update(self, if time_zone is not None: self.set_time_zone(time_zone) - async def update(self, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - elevation: Optional[int] = None, - unit_system: Optional[str] = None, - location_name: Optional[str] = None, - time_zone: Optional[str] = None) -> None: + async def update(self, **kwargs: Any) -> None: """Update the configuration from a dictionary. Async friendly. """ - from homeassistant.config import SOURCE_STORAGE - self._update(SOURCE_STORAGE, latitude, longitude, elevation, - unit_system, location_name, time_zone) + self._update(source=SOURCE_STORAGE, **kwargs) await self.store() self.hass.bus.async_fire( - EVENT_CORE_CONFIG_UPDATE + EVENT_CORE_CONFIG_UPDATE, kwargs ) async def load(self) -> None: """Load [homeassistant] core config.""" - from homeassistant.config import SOURCE_STORAGE store = self.hass.helpers.storage.Store( CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True) data = await store.async_load() if not data: return - self._update(SOURCE_STORAGE, data['latitude'], data['longitude'], - data['elevation'], data['unit_system'], - data['location_name'], data['time_zone']) + self._update(source=SOURCE_STORAGE, **data) async def store(self) -> None: """Store [homeassistant] core config.""" diff --git a/tests/test_config.py b/tests/test_config.py index 4fd43133093099..c5d70b9d8a7349 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -11,7 +11,8 @@ from voluptuous import MultipleInvalid, Invalid import yaml -from homeassistant.core import DOMAIN, HomeAssistantError, Config +from homeassistant.core import ( + DOMAIN, SOURCE_STORAGE, Config, HomeAssistantError) import homeassistant.config as config_util from homeassistant.loader import async_get_integration from homeassistant.const import ( @@ -439,7 +440,32 @@ async def test_loading_configuration_from_storage(hass, hass_storage): assert hass.config.time_zone.zone == 'Europe/Copenhagen' assert len(hass.config.whitelist_external_dirs) == 2 assert '/tmp' in hass.config.whitelist_external_dirs - assert hass.config.config_source == config_util.SOURCE_STORAGE + assert hass.config.config_source == SOURCE_STORAGE + + +async def test_updating_configuration(hass, hass_storage): + """Test updating configuration stores the new configuration.""" + core_data = { + 'data': { + 'elevation': 10, + 'latitude': 55, + 'location_name': 'Home', + 'longitude': 13, + 'time_zone': 'Europe/Copenhagen', + 'unit_system': 'metric' + }, + 'key': 'homeassistant.core_config', + 'version': 1 + } + hass_storage["homeassistant.core_config"] = dict(core_data) + await config_util.async_process_ha_core_config( + hass, {'whitelist_external_dirs': '/tmp'}) + await hass.config.update(latitude=50) + + new_core_data = dict(core_data) + new_core_data['data']['latitude'] = 50 + assert hass_storage["homeassistant.core_config"] == new_core_data + assert hass.config.latitude == 50 async def test_override_stored_configuration(hass, hass_storage): diff --git a/tests/test_core.py b/tests/test_core.py index caa411d59b2120..bf49ee62f454c0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -23,7 +23,8 @@ __version__, EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, CONF_UNIT_SYSTEM, ATTR_NOW, EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, ATTR_SECONDS, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_CLOSE, - EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_CALL_SERVICE) + EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_CALL_SERVICE, + EVENT_CORE_CONFIG_UPDATE) from tests.common import get_test_home_assistant, async_mock_service @@ -947,6 +948,32 @@ def test_is_allowed_path(self): with pytest.raises(AssertionError): self.config.is_allowed_path(None) + def test_event_on_update(self): + """Test that event is fired on update.""" + events = [] + + @ha.callback + def callback(event): + events.append(event) + + self.hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, callback) + + assert self.config.latitude != 12 + + run_coroutine_threadsafe( + self.config.update(latitude=12), self.hass.loop).result() + self.hass.block_till_done() + + assert self.config.latitude == 12 + assert len(events) == 1 + assert events[0].data == {'latitude': 12} + + +def test_bad_timezone_raises_value_error(hass): + """Test bad timezone raises ValueError.""" + with pytest.raises(ValueError): + hass.config.set_time_zone('not_a_timezone') + @patch('homeassistant.core.monotonic') def test_create_timer(mock_monotonic, loop): From dd4a37441ebe3a64bf9db53b9d600b4d81fcabbd Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 20 May 2019 08:04:00 +0200 Subject: [PATCH 4/5] Fix tests --- tests/test_core.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index bf49ee62f454c0..4fcf02d08b4da3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -948,25 +948,25 @@ def test_is_allowed_path(self): with pytest.raises(AssertionError): self.config.is_allowed_path(None) - def test_event_on_update(self): - """Test that event is fired on update.""" - events = [] - @ha.callback - def callback(event): - events.append(event) +async def test_event_on_update(hass, hass_storage): + """Test that event is fired on update.""" + events = [] - self.hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, callback) + @ha.callback + def callback(event): + events.append(event) - assert self.config.latitude != 12 + hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, callback) - run_coroutine_threadsafe( - self.config.update(latitude=12), self.hass.loop).result() - self.hass.block_till_done() + assert hass.config.latitude != 12 - assert self.config.latitude == 12 - assert len(events) == 1 - assert events[0].data == {'latitude': 12} + await hass.config.update(latitude=12) + await hass.async_block_till_done() + + assert hass.config.latitude == 12 + assert len(events) == 1 + assert events[0].data == {'latitude': 12} def test_bad_timezone_raises_value_error(hass): From 62543ba208665b9bcc9d92a109275c3ac01e3bff Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 20 May 2019 17:08:28 +0200 Subject: [PATCH 5/5] Review comments --- homeassistant/config.py | 2 +- homeassistant/core.py | 6 +++--- tests/common.py | 1 - tests/test_config.py | 7 ++----- tests/test_core.py | 8 +------- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 18266ec0efbcd4..adcd0e59634bef 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -513,7 +513,7 @@ async def async_process_ha_core_config( auth_conf, mfa_conf)) - await hass.config.load() + await hass.config.async_load() hac = hass.config diff --git a/homeassistant/core.py b/homeassistant/core.py index 6324d8701e2713..4dd84cc1a46bcd 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1315,12 +1315,12 @@ async def update(self, **kwargs: Any) -> None: Async friendly. """ self._update(source=SOURCE_STORAGE, **kwargs) - await self.store() + await self.async_store() self.hass.bus.async_fire( EVENT_CORE_CONFIG_UPDATE, kwargs ) - async def load(self) -> None: + async def async_load(self) -> None: """Load [homeassistant] core config.""" store = self.hass.helpers.storage.Store( CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True) @@ -1330,7 +1330,7 @@ async def load(self) -> None: self._update(source=SOURCE_STORAGE, **data) - async def store(self) -> None: + async def async_store(self) -> None: """Store [homeassistant] core config.""" time_zone = dt_util.UTC.zone if self.time_zone and getattr(self.time_zone, 'zone'): diff --git a/tests/common.py b/tests/common.py index 572cd19a0066b7..f7b3bc46bbdb8c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -122,7 +122,6 @@ def stop_hass(): async def async_test_home_assistant(loop): """Return a Home Assistant object pointing at test config dir.""" hass = ha.HomeAssistant(loop) - hass.config.async_load = Mock() store = auth_store.AuthStore(hass) hass.auth = auth.AuthManager(hass, store, {}, {}) ensure_auth_manager_loaded(hass.auth) diff --git a/tests/test_config.py b/tests/test_config.py index c5d70b9d8a7349..42386cc1f4cebe 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,7 @@ """Test config utils.""" # pylint: disable=protected-access import asyncio +import copy import os import unittest.mock as mock from collections import OrderedDict @@ -462,7 +463,7 @@ async def test_updating_configuration(hass, hass_storage): hass, {'whitelist_external_dirs': '/tmp'}) await hass.config.update(latitude=50) - new_core_data = dict(core_data) + new_core_data = copy.deepcopy(core_data) new_core_data['data']['latitude'] = 50 assert hass_storage["homeassistant.core_config"] == new_core_data assert hass.config.latitude == 50 @@ -543,10 +544,6 @@ async def test_loading_configuration_temperature_unit(hass): async def test_loading_configuration_from_packages(hass): """Test loading packages config onto hass object config.""" - hass.config = mock.Mock() - hass.config.load.return_value = asyncio.Future() - hass.config.load.return_value.set_result(None) - await config_util.async_process_ha_core_config(hass, { 'latitude': 39, 'longitude': -1, diff --git a/tests/test_core.py b/tests/test_core.py index 4fcf02d08b4da3..101396dd05eee7 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -872,15 +872,9 @@ class TestConfig(unittest.TestCase): # pylint: disable=invalid-name def setUp(self): """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.config = ha.Config(self.hass) + self.config = ha.Config(None) assert self.config.config_dir is None - # pylint: disable=invalid-name - def tearDown(self): - """Stop down stuff we started.""" - self.hass.stop() - def test_path_with_file(self): """Test get_config_path method.""" self.config.config_dir = '/tmp/ha-config'