diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3b0a900d51e..da7886ad1e8 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -395,6 +395,10 @@ def async_from_config_dict(config: Dict[str, Any], if not loader.PREPARED: yield from hass.loop.run_in_executor(None, loader.prepare, hass) + # Merge packages + conf_util.merge_packages_config( + config, core_config.get(conf_util.CONF_PACKAGES, {})) + # Make a copy because we are mutating it. # Use OrderedDict in case original one was one. # Convert values to dictionaries if they are None diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 05cbc5d0a80..1cca7c8d790 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -42,7 +42,7 @@ _SCRIPT_ENTRY_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - vol.Required(DOMAIN): vol.Schema({cv.slug: _SCRIPT_ENTRY_SCHEMA}) + DOMAIN: vol.Schema({cv.slug: _SCRIPT_ENTRY_SCHEMA}) }, extra=vol.ALLOW_EXTRA) SCRIPT_SERVICE_SCHEMA = vol.Schema(dict) diff --git a/homeassistant/config.py b/homeassistant/config.py index f2f642de8ea..eb29212a67d 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,22 +1,23 @@ """Module to help with parsing and generating configuration files.""" import asyncio +from collections import OrderedDict import logging import os import shutil from types import MappingProxyType - # pylint: disable=unused-import from typing import Any, Tuple # NOQA import voluptuous as vol from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, 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_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS, __version__) -from homeassistant.core import valid_entity_id +from homeassistant.core import valid_entity_id, DOMAIN as CONF_CORE from homeassistant.exceptions import HomeAssistantError +from homeassistant.loader import get_component from homeassistant.util.yaml import load_yaml import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import set_customize @@ -101,6 +102,11 @@ def _valid_customize(value): return value +PACKAGES_CONFIG_SCHEMA = vol.Schema({ + cv.slug: vol.Schema( # Package names are slugs + {cv.slug: vol.Any(dict, list)}) # Only slugs for component names +}) + CORE_CONFIG_SCHEMA = vol.Schema({ CONF_NAME: vol.Coerce(str), CONF_LATITUDE: cv.latitude, @@ -111,6 +117,7 @@ CORE_CONFIG_SCHEMA = vol.Schema({ CONF_TIME_ZONE: cv.time_zone, vol.Required(CONF_CUSTOMIZE, default=MappingProxyType({})): _valid_customize, + vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA, }) @@ -357,3 +364,91 @@ def async_process_ha_core_config(hass, config): _LOGGER.warning( 'Incomplete core config. Auto detected %s', ', '.join('{}: {}'.format(key, val) for key, val in discovered)) + + +def _log_pkg_error(package, component, config, message): + """Log an error while merging.""" + message = "Package {} setup failed. Component {} {}".format( + package, component, message) + + pack_config = config[CONF_CORE][CONF_PACKAGES].get(package, config) + message += " (See {}:{}). ".format( + getattr(pack_config, '__config_file__', '?'), + getattr(pack_config, '__line__', '?')) + + _LOGGER.error(message) + + +def _identify_config_schema(module): + """Extract the schema and identify list or dict based.""" + try: + schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] + except (AttributeError, KeyError): + return (None, None) + t_schema = str(schema) + if (t_schema.startswith('