From 41f558b181075980c551796c76cd32480c32e539 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 1 Mar 2017 05:33:19 +0100 Subject: [PATCH] Bootstrap / Component setup async (#6264) * Bootstrap / Entiy setup async * Cleanup add_job stuff / return task/future object * Address paulus comments / part 1 * fix install pip * Cleanup bootstrap / move config stuff to config.py * Make demo async * Further bootstrap improvement * Address Martin's comments * Fix initial tests * Fix final tests * Fix bug with prepare loader * Remove no longer needed things * Log error when invalid config * More cleanup * Cleanups platform events & fix lint * Use a non blocking add_entities callback for platform * Fix Autoamtion is setup befor entity is ready * Better automation fix * Address paulus comments * Typo * fix lint * rename functions * fix tests * fix test * change exceptions * fix spell --- homeassistant/bootstrap.py | 478 +++++++----------- .../alarm_control_panel/envisalink.py | 2 +- .../components/alarm_control_panel/mqtt.py | 2 +- .../components/automation/__init__.py | 26 +- .../components/binary_sensor/envisalink.py | 2 +- .../components/binary_sensor/ffmpeg_motion.py | 2 +- .../components/binary_sensor/ffmpeg_noise.py | 2 +- .../components/binary_sensor/mqtt.py | 2 +- .../components/binary_sensor/template.py | 2 +- .../components/binary_sensor/threshold.py | 2 +- homeassistant/components/camera/ffmpeg.py | 2 +- homeassistant/components/camera/generic.py | 2 +- homeassistant/components/camera/mjpeg.py | 2 +- homeassistant/components/camera/synology.py | 2 +- homeassistant/components/camera/zoneminder.py | 2 +- .../components/climate/generic_thermostat.py | 2 +- homeassistant/components/cover/mqtt.py | 2 +- homeassistant/components/demo.py | 170 ++++--- .../components/device_tracker/__init__.py | 5 +- homeassistant/components/fan/mqtt.py | 2 +- .../image_processing/microsoft_face_detect.py | 2 +- .../microsoft_face_identify.py | 2 +- .../image_processing/openalpr_cloud.py | 2 +- .../image_processing/openalpr_local.py | 2 +- homeassistant/components/light/mqtt.py | 2 +- homeassistant/components/light/mqtt_json.py | 2 +- .../components/light/mqtt_template.py | 2 +- homeassistant/components/light/rflink.py | 19 +- homeassistant/components/lock/mqtt.py | 2 +- .../components/media_player/anthemav.py | 2 +- .../components/media_player/squeezebox.py | 2 +- .../components/media_player/universal.py | 2 +- homeassistant/components/scene/__init__.py | 1 - .../components/scene/homeassistant.py | 3 +- homeassistant/components/script.py | 2 +- .../components/sensor/api_streams.py | 2 +- homeassistant/components/sensor/dnsip.py | 2 +- homeassistant/components/sensor/dsmr.py | 2 +- homeassistant/components/sensor/envisalink.py | 2 +- homeassistant/components/sensor/min_max.py | 2 +- homeassistant/components/sensor/moon.py | 2 +- homeassistant/components/sensor/mqtt.py | 2 +- homeassistant/components/sensor/mqtt_room.py | 2 +- homeassistant/components/sensor/random.py | 2 +- homeassistant/components/sensor/rflink.py | 9 +- homeassistant/components/sensor/statistics.py | 2 +- homeassistant/components/sensor/template.py | 2 +- homeassistant/components/sensor/time_date.py | 2 +- homeassistant/components/sensor/worldclock.py | 2 +- homeassistant/components/sensor/yr.py | 2 +- homeassistant/components/switch/hook.py | 2 +- homeassistant/components/switch/mqtt.py | 2 +- homeassistant/components/switch/rest.py | 2 +- homeassistant/components/switch/rflink.py | 2 +- homeassistant/components/switch/template.py | 2 +- homeassistant/components/zwave/__init__.py | 2 +- homeassistant/config.py | 120 ++++- homeassistant/core.py | 32 +- homeassistant/helpers/discovery.py | 33 +- homeassistant/helpers/entity_component.py | 73 ++- homeassistant/loader.py | 35 -- homeassistant/scripts/check_config.py | 6 +- tests/common.py | 30 +- .../alarm_control_panel/test_mqtt.py | 10 - tests/components/automation/test_event.py | 4 +- tests/components/automation/test_init.py | 4 +- tests/components/automation/test_mqtt.py | 5 +- .../automation/test_numeric_state.py | 4 +- tests/components/automation/test_state.py | 5 +- tests/components/automation/test_sun.py | 7 +- tests/components/automation/test_template.py | 5 +- tests/components/automation/test_time.py | 5 +- tests/components/automation/test_zone.py | 4 +- tests/components/binary_sensor/test_mqtt.py | 7 +- tests/components/camera/test_uvc.py | 5 +- .../climate/test_generic_thermostat.py | 40 +- tests/components/config/test_core.py | 3 + tests/components/config/test_init.py | 4 +- tests/components/cover/test_mqtt.py | 11 +- tests/components/cover/test_rfxtrx.py | 4 +- .../components/device_tracker/test_asuswrt.py | 5 +- tests/components/device_tracker/test_ddwrt.py | 5 +- tests/components/device_tracker/test_mqtt.py | 1 - .../device_tracker/test_upc_connect.py | 5 +- tests/components/http/test_init.py | 69 +-- tests/components/light/test_demo.py | 4 +- tests/components/light/test_mqtt.py | 8 - tests/components/light/test_mqtt_json.py | 7 - tests/components/light/test_mqtt_template.py | 7 - tests/components/light/test_rfxtrx.py | 4 +- tests/components/lock/test_mqtt.py | 3 - .../components/media_player/test_universal.py | 2 - tests/components/mqtt/test_server.py | 17 +- tests/components/notify/test_demo.py | 16 - tests/components/sensor/test_mqtt.py | 6 +- tests/components/sensor/test_pilight.py | 5 +- tests/components/sensor/test_rfxtrx.py | 4 +- tests/components/switch/test_mqtt.py | 3 - tests/components/switch/test_rfxtrx.py | 4 +- tests/components/test_input_boolean.py | 4 +- tests/components/test_panel_custom.py | 3 + tests/components/test_rfxtrx.py | 4 +- tests/components/test_script.py | 4 +- tests/components/test_zone.py | 12 +- tests/helpers/test_discovery.py | 23 +- tests/helpers/test_entity_component.py | 6 + tests/helpers/test_restore_state.py | 5 +- tests/test_bootstrap.py | 83 ++- tests/test_loader.py | 30 -- 109 files changed, 764 insertions(+), 848 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index cb32fc887c9..b1233594f89 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -4,38 +4,41 @@ import logging import logging.handlers import os import sys +from time import time from collections import OrderedDict from types import ModuleType from typing import Any, Optional, Dict import voluptuous as vol -from voluptuous.humanize import humanize_error import homeassistant.components as core_components from homeassistant.components import persistent_notification import homeassistant.config as conf_util +from homeassistant.config import async_notify_setup_error import homeassistant.core as core from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE import homeassistant.loader as loader import homeassistant.util.package as pkg_util -from homeassistant.util.async import ( - run_coroutine_threadsafe, run_callback_threadsafe) +from homeassistant.util.async import run_coroutine_threadsafe from homeassistant.util.logging import AsyncHandler from homeassistant.util.yaml import clear_secret_cache from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import ( - event_decorators, service, config_per_platform, extract_domain_configs) +from homeassistant.helpers import event_decorators, service from homeassistant.helpers.signal import async_register_signal_handling _LOGGER = logging.getLogger(__name__) ATTR_COMPONENT = 'component' +DATA_SETUP = 'setup_tasks' +DATA_PIP_LOCK = 'pip_lock' + ERROR_LOG_FILENAME = 'home-assistant.log' -DATA_PERSISTENT_ERRORS = 'bootstrap_persistent_errors' -HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)' + +FIRST_INIT_COMPONENT = set(( + 'recorder', 'mqtt', 'mqtt_eventstream', 'logger', 'introduction')) def setup_component(hass: core.HomeAssistant, domain: str, @@ -52,49 +55,82 @@ def async_setup_component(hass: core.HomeAssistant, domain: str, This method is a coroutine. """ - if domain in hass.config.components: - _LOGGER.debug('Component %s already set up.', domain) - return True + setup_tasks = hass.data.get(DATA_SETUP) - if not loader.PREPARED: - yield from hass.loop.run_in_executor(None, loader.prepare, hass) + if setup_tasks is not None and domain in setup_tasks: + return (yield from setup_tasks[domain]) if config is None: config = {} - components = loader.load_order_component(domain) + if setup_tasks is None: + setup_tasks = hass.data[DATA_SETUP] = {} - # OrderedSet is empty if component or dependencies could not be resolved - if not components: - _async_persistent_notification(hass, domain, True) - return False + task = setup_tasks[domain] = hass.async_add_job( + _async_setup_component(hass, domain, config)) - for component in components: - res = yield from _async_setup_component(hass, component, config) - if not res: - _LOGGER.error('Component %s failed to setup', component) - _async_persistent_notification(hass, component, True) - return False + return (yield from task) + + +@asyncio.coroutine +def _async_process_requirements(hass: core.HomeAssistant, name: str, + requirements) -> bool: + """Install the requirements for a component. + + This method is a coroutine. + """ + if hass.config.skip_pip: + return True + + pip_lock = hass.data.get(DATA_PIP_LOCK) + if pip_lock is None: + pip_lock = hass.data[DATA_PIP_LOCK] = asyncio.Lock(loop=hass.loop) + + def pip_install(mod): + """Install packages.""" + return pkg_util.install_package(mod, target=hass.config.path('deps')) + + with (yield from pip_lock): + for req in requirements: + ret = yield from hass.loop.run_in_executor(None, pip_install, req) + if not ret: + _LOGGER.error('Not initializing %s because could not install ' + 'dependency %s', name, req) + async_notify_setup_error(hass, name) + return False return True -def _handle_requirements(hass: core.HomeAssistant, component, - name: str) -> bool: - """Install the requirements for a component. +@asyncio.coroutine +def _async_process_dependencies(hass, config, name, dependencies): + """Ensure all dependencies are set up.""" + blacklisted = [dep for dep in dependencies + if dep in loader.DEPENDENCY_BLACKLIST] - This method needs to run in an executor. - """ - if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'): + if blacklisted: + _LOGGER.error('Unable to setup dependencies of %s: ' + 'found blacklisted dependencies: %s', + name, ', '.join(blacklisted)) + return False + + tasks = [async_setup_component(hass, dep, config) for dep + in dependencies] + + if not tasks: return True - for req in component.REQUIREMENTS: - if not pkg_util.install_package(req, target=hass.config.path('deps')): - _LOGGER.error('Not initializing %s because could not install ' - 'dependency %s', name, req) - _async_persistent_notification(hass, name) - return False + results = yield from asyncio.gather(*tasks, loop=hass.loop) + failed = [dependencies[idx] for idx, res + in enumerate(results) if not res] + + if failed: + _LOGGER.error('Unable to setup dependencies of %s. ' + 'Setup failed for dependencies: %s', + name, ', '.join(failed)) + + return False return True @@ -104,172 +140,78 @@ def _async_setup_component(hass: core.HomeAssistant, """Setup a component for Home Assistant. This method is a coroutine. + + hass: Home Assistant instance. + domain: Domain of component to setup. + config: The Home Assistant configuration. """ - # pylint: disable=too-many-return-statements - if domain in hass.config.components: - return True + def log_error(msg): + """Log helper.""" + _LOGGER.error('Setup failed for %s: %s', domain, msg) + async_notify_setup_error(hass, domain, True) - setup_lock = hass.data.get('setup_lock') - if setup_lock is None: - setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop) + # Validate no circular dependencies + components = loader.load_order_component(domain) - setup_progress = hass.data.get('setup_progress') - if setup_progress is None: - setup_progress = hass.data['setup_progress'] = [] - - if domain in setup_progress: - _LOGGER.error('Attempt made to setup %s during setup of %s', - domain, domain) - _async_persistent_notification(hass, domain, True) + # OrderedSet is empty if component or dependencies could not be resolved + if not components: + log_error('Unable to resolve component or dependencies') return False - try: - # Used to indicate to discovery that a setup is ongoing and allow it - # to wait till it is done. - did_lock = False - if not setup_lock.locked(): - yield from setup_lock.acquire() - did_lock = True - - setup_progress.append(domain) - config = yield from async_prepare_setup_component(hass, config, domain) - - if config is None: - return False - - component = loader.get_component(domain) - if component is None: - _async_persistent_notification(hass, domain) - return False - - async_comp = hasattr(component, 'async_setup') - - try: - _LOGGER.info("Setting up %s", domain) - if async_comp: - result = yield from component.async_setup(hass, config) - else: - result = yield from hass.loop.run_in_executor( - None, component.setup, hass, config) - except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error during setup of component %s', domain) - _async_persistent_notification(hass, domain, True) - return False - - if result is False: - _LOGGER.error('component %s failed to initialize', domain) - _async_persistent_notification(hass, domain, True) - return False - elif result is not True: - _LOGGER.error('component %s did not return boolean if setup ' - 'was successful. Disabling component.', domain) - _async_persistent_notification(hass, domain, True) - loader.set_component(domain, None) - return False - - hass.config.components.add(component.DOMAIN) - - hass.bus.async_fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} - ) - - return True - finally: - setup_progress.remove(domain) - if did_lock: - setup_lock.release() - - -def prepare_setup_component(hass: core.HomeAssistant, config: dict, - domain: str): - """Prepare setup of a component and return processed config.""" - return run_coroutine_threadsafe( - async_prepare_setup_component(hass, config, domain), loop=hass.loop - ).result() - - -@asyncio.coroutine -def async_prepare_setup_component(hass: core.HomeAssistant, config: dict, - domain: str): - """Prepare setup of a component and return processed config. - - This method is a coroutine. - """ - # pylint: disable=too-many-return-statements component = loader.get_component(domain) - missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) - if dep not in hass.config.components] - if missing_deps: - _LOGGER.error( - 'Not initializing %s because not all dependencies loaded: %s', - domain, ", ".join(missing_deps)) - return None + processed_config = \ + conf_util.async_process_component_config(hass, config, domain) - if hasattr(component, 'CONFIG_SCHEMA'): - try: - config = component.CONFIG_SCHEMA(config) - except vol.Invalid as ex: - async_log_exception(ex, domain, config, hass) - return None + if processed_config is None: + log_error('Invalid config') + return False - elif hasattr(component, 'PLATFORM_SCHEMA'): - platforms = [] - for p_name, p_config in config_per_platform(config, domain): - # Validate component specific platform schema - try: - p_validated = component.PLATFORM_SCHEMA(p_config) - except vol.Invalid as ex: - async_log_exception(ex, domain, config, hass) - continue + if not hass.config.skip_pip and hasattr(component, 'REQUIREMENTS'): + req_success = yield from _async_process_requirements( + hass, domain, component.REQUIREMENTS) + if not req_success: + log_error('Could not install all requirements.') + return False - # Not all platform components follow same pattern for platforms - # So if p_name is None we are not going to validate platform - # (the automation component is one of them) - if p_name is None: - platforms.append(p_validated) - continue + if hasattr(component, 'DEPENDENCIES'): + dep_success = yield from _async_process_dependencies( + hass, config, domain, component.DEPENDENCIES) - platform = yield from async_prepare_setup_platform( - hass, config, domain, p_name) + if not dep_success: + log_error('Could not setup all dependencies.') + return False - if platform is None: - continue + async_comp = hasattr(component, 'async_setup') - # Validate platform specific schema - if hasattr(platform, 'PLATFORM_SCHEMA'): - try: - # pylint: disable=no-member - p_validated = platform.PLATFORM_SCHEMA(p_validated) - except vol.Invalid as ex: - async_log_exception(ex, '{}.{}'.format(domain, p_name), - p_validated, hass) - continue + try: + _LOGGER.info("Setting up %s", domain) + if async_comp: + result = yield from component.async_setup(hass, processed_config) + else: + result = yield from hass.loop.run_in_executor( + None, component.setup, hass, processed_config) + except Exception: # pylint: disable=broad-except + _LOGGER.exception('Error during setup of component %s', domain) + async_notify_setup_error(hass, domain, True) + return False - platforms.append(p_validated) + if result is False: + log_error('Component failed to initialize.') + return False + elif result is not True: + log_error('Component did not return boolean if setup was successful. ' + 'Disabling component.') + loader.set_component(domain, None) + return False - # Create a copy of the configuration with all config for current - # component removed and add validated config back in. - filter_keys = extract_domain_configs(config, domain) - config = {key: value for key, value in config.items() - if key not in filter_keys} - config[domain] = platforms + hass.config.components.add(component.DOMAIN) - res = yield from hass.loop.run_in_executor( - None, _handle_requirements, hass, component, domain) - if not res: - return None + hass.bus.async_fire( + EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} + ) - return config - - -def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, - platform_name: str) -> Optional[ModuleType]: - """Load a platform and makes sure dependencies are setup.""" - return run_coroutine_threadsafe( - async_prepare_setup_platform(hass, config, domain, platform_name), - loop=hass.loop - ).result() + return True @asyncio.coroutine @@ -280,17 +222,19 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, This method is a coroutine. """ - if not loader.PREPARED: - yield from hass.loop.run_in_executor(None, loader.prepare, hass) - platform_path = PLATFORM_FORMAT.format(domain, platform_name) + def log_error(msg): + """Log helper.""" + _LOGGER.error('Unable to prepare setup for platform %s: %s', + platform_path, msg) + async_notify_setup_error(hass, platform_path) + platform = loader.get_platform(domain, platform_name) # Not found if platform is None: - _LOGGER.error('Unable to find platform %s', platform_path) - _async_persistent_notification(hass, platform_path) + log_error('Unable to find platform') return None # Already loaded @@ -298,25 +242,22 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, return platform # Load dependencies - for component in getattr(platform, 'DEPENDENCIES', []): - if component in loader.DEPENDENCY_BLACKLIST: - raise HomeAssistantError( - '{} is not allowed to be a dependency.'.format(component)) + if hasattr(platform, 'DEPENDENCIES'): + dep_success = yield from _async_process_dependencies( + hass, config, platform_path, platform.DEPENDENCIES) - res = yield from async_setup_component(hass, component, config) - if not res: - _LOGGER.error( - 'Unable to prepare setup for platform %s because ' - 'dependency %s could not be initialized', platform_path, - component) - _async_persistent_notification(hass, platform_path, True) + if not dep_success: + log_error('Could not setup all dependencies.') + return False + + if not hass.config.skip_pip and hasattr(platform, 'REQUIREMENTS'): + req_success = yield from _async_process_requirements( + hass, platform_path, platform.REQUIREMENTS) + + if not req_success: + log_error('Could not install all requirements.') return None - res = yield from hass.loop.run_in_executor( - None, _handle_requirements, hass, platform, platform_path) - if not res: - return None - return platform @@ -339,23 +280,14 @@ def from_config_dict(config: Dict[str, Any], hass.config.config_dir = config_dir mount_local_lib_path(config_dir) - @asyncio.coroutine - def _async_init_from_config_dict(future): - try: - re_hass = yield from async_from_config_dict( - config, hass, config_dir, enable_log, verbose, skip_pip, - log_rotate_days) - future.set_result(re_hass) - # pylint: disable=broad-except - except Exception as exc: - future.set_exception(exc) - # run task - future = asyncio.Future(loop=hass.loop) - hass.async_add_job(_async_init_from_config_dict(future)) - hass.loop.run_until_complete(future) + hass = hass.loop.run_until_complete( + async_from_config_dict( + config, hass, config_dir, enable_log, verbose, skip_pip, + log_rotate_days) + ) - return future.result() + return hass @asyncio.coroutine @@ -372,19 +304,15 @@ def async_from_config_dict(config: Dict[str, Any], Dynamically loads required components and its dependencies. This method is a coroutine. """ + start = time() hass.async_track_tasks() - setup_lock = hass.data.get('setup_lock') - if setup_lock is None: - setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop) - - yield from setup_lock.acquire() core_config = config.get(core.DOMAIN, {}) try: yield from conf_util.async_process_ha_core_config(hass, core_config) except vol.Invalid as ex: - async_log_exception(ex, 'homeassistant', core_config, hass) + conf_util.async_log_exception(ex, 'homeassistant', core_config, hass) return None yield from hass.loop.run_in_executor( @@ -433,20 +361,25 @@ def async_from_config_dict(config: Dict[str, Any], event_decorators.HASS = hass service.HASS = hass - # Setup the components - dependency_blacklist = loader.DEPENDENCY_BLACKLIST - set(components) + # stage 1 + for component in components: + if component not in FIRST_INIT_COMPONENT: + continue + hass.async_add_job(async_setup_component(hass, component, config)) - for domain in loader.load_order_components(components): - if domain in dependency_blacklist: - raise HomeAssistantError( - '{} is not allowed to be a dependency'.format(domain)) + yield from hass.async_block_till_done() - yield from _async_setup_component(hass, domain, config) - - setup_lock.release() + # stage 2 + for component in components: + if component in FIRST_INIT_COMPONENT: + continue + hass.async_add_job(async_setup_component(hass, component, config)) yield from hass.async_stop_track_tasks() + stop = time() + _LOGGER.info('Home Assistant initialized in %ss', round(stop-start, 2)) + async_register_signal_handling(hass) return hass @@ -464,22 +397,13 @@ def from_config_file(config_path: str, if hass is None: hass = core.HomeAssistant() - @asyncio.coroutine - def _async_init_from_config_file(future): - try: - re_hass = yield from async_from_config_file( - config_path, hass, verbose, skip_pip, log_rotate_days) - future.set_result(re_hass) - # pylint: disable=broad-except - except Exception as exc: - future.set_exception(exc) - # run task - future = asyncio.Future(loop=hass.loop) - hass.loop.create_task(_async_init_from_config_file(future)) - hass.loop.run_until_complete(future) + hass = hass.loop.run_until_complete( + async_from_config_file( + config_path, hass, verbose, skip_pip, log_rotate_days) + ) - return future.result() + return hass @asyncio.coroutine @@ -588,62 +512,6 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False, 'Unable to setup error log %s (access denied)', err_log_path) -def log_exception(ex, domain, config, hass): - """Generate log exception for config validation.""" - run_callback_threadsafe( - hass.loop, async_log_exception, ex, domain, config, hass).result() - - -@core.callback -def _async_persistent_notification(hass: core.HomeAssistant, component: str, - link: Optional[bool]=False): - """Print a persistent notification. - - This method must be run in the event loop. - """ - errors = hass.data.get(DATA_PERSISTENT_ERRORS) - - if errors is None: - errors = hass.data[DATA_PERSISTENT_ERRORS] = {} - - errors[component] = errors.get(component) or link - _lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name) - if link else name for name, link in errors.items()] - message = ('The following components and platforms could not be set up:\n' - '* ' + '\n* '.join(list(_lst)) + '\nPlease check your config') - persistent_notification.async_create( - hass, message, 'Invalid config', 'invalid_config') - - -@core.callback -def async_log_exception(ex, domain, config, hass): - """Generate log exception for config validation. - - This method must be run in the event loop. - """ - message = 'Invalid config for [{}]: '.format(domain) - if hass is not None: - _async_persistent_notification(hass, domain, True) - - if 'extra keys not allowed' in ex.error_message: - message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ - .format(ex.path[-1], domain, domain, - '->'.join(str(m) for m in ex.path)) - else: - message += '{}.'.format(humanize_error(config, ex)) - - domain_config = config.get(domain, config) - message += " (See {}, line {}). ".format( - getattr(domain_config, '__config_file__', '?'), - getattr(domain_config, '__line__', '?')) - - if domain != 'homeassistant': - message += ('Please check the docs at ' - 'https://home-assistant.io/components/{}/'.format(domain)) - - _LOGGER.error(message) - - def mount_local_lib_path(config_dir: str) -> str: """Add local library to Python Path. diff --git a/homeassistant/components/alarm_control_panel/envisalink.py b/homeassistant/components/alarm_control_panel/envisalink.py index cd5bddbad49..248b0124d77 100644 --- a/homeassistant/components/alarm_control_panel/envisalink.py +++ b/homeassistant/components/alarm_control_panel/envisalink.py @@ -55,7 +55,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): ) devices.append(device) - yield from async_add_devices(devices) + async_add_devices(devices) @callback def alarm_keypress_handler(service): diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index 455f60319c6..b22f50b6575 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -46,7 +46,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup the MQTT platform.""" - yield from async_add_devices([MqttAlarm( + async_add_devices([MqttAlarm( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index bebace6d827..0e734d7214d 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -28,8 +28,6 @@ import homeassistant.helpers.config_validation as cv DOMAIN = 'automation' ENTITY_ID_FORMAT = DOMAIN + '.{}' -DEPENDENCIES = ['group'] - GROUP_NAME_ALL_AUTOMATIONS = 'all automations' CONF_ALIAS = 'alias' @@ -226,7 +224,7 @@ class AutomationEntity(ToggleEntity): """Entity to show status of entity.""" def __init__(self, name, async_attach_triggers, cond_func, async_action, - hidden): + hidden, initial_state): """Initialize an automation entity.""" self._name = name self._async_attach_triggers = async_attach_triggers @@ -236,6 +234,7 @@ class AutomationEntity(ToggleEntity): self._enabled = False self._last_triggered = None self._hidden = hidden + self._initial_state = initial_state @property def name(self): @@ -264,6 +263,12 @@ class AutomationEntity(ToggleEntity): """Return True if entity is on.""" return self._enabled + @asyncio.coroutine + def async_added_to_hass(self) -> None: + """Startup if initial_state.""" + if self._initial_state: + yield from self.async_enable() + @asyncio.coroutine def async_turn_on(self, **kwargs) -> None: """Turn the entity on and update the state.""" @@ -322,7 +327,6 @@ def _async_process_config(hass, config, component): This method is a coroutine. """ entities = [] - tasks = [] for config_key in extract_domain_configs(config, DOMAIN): conf = config[config_key] @@ -332,6 +336,7 @@ def _async_process_config(hass, config, component): list_no) hidden = config_block[CONF_HIDE_ENTITY] + initial_state = config_block[CONF_INITIAL_STATE] action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) @@ -348,15 +353,14 @@ def _async_process_config(hass, config, component): async_attach_triggers = partial( _async_process_trigger, hass, config, - config_block.get(CONF_TRIGGER, []), name) - entity = AutomationEntity(name, async_attach_triggers, cond_func, - action, hidden) - if config_block[CONF_INITIAL_STATE]: - tasks.append(entity.async_enable()) + config_block.get(CONF_TRIGGER, []), name + ) + entity = AutomationEntity( + name, async_attach_triggers, cond_func, action, hidden, + initial_state) + entities.append(entity) - if tasks: - yield from asyncio.wait(tasks, loop=hass.loop) if entities: yield from component.async_add_entities(entities) diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/binary_sensor/envisalink.py index 279dadf120f..acc71da3f46 100644 --- a/homeassistant/components/binary_sensor/envisalink.py +++ b/homeassistant/components/binary_sensor/envisalink.py @@ -37,7 +37,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): ) devices.append(device) - yield from async_add_devices(devices) + async_add_devices(devices) class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/binary_sensor/ffmpeg_motion.py index 3dd3f351227..418a6342172 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_motion.py +++ b/homeassistant/components/binary_sensor/ffmpeg_motion.py @@ -57,7 +57,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # generate sensor object entity = FFmpegMotion(hass, manager, config) - yield from async_add_devices([entity]) + async_add_devices([entity]) class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/binary_sensor/ffmpeg_noise.py index af5c64186f6..c3400150f74 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_noise.py +++ b/homeassistant/components/binary_sensor/ffmpeg_noise.py @@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # generate sensor object entity = FFmpegNoise(hass, manager, config) - yield from async_add_devices([entity]) + async_add_devices([entity]) class FFmpegNoise(FFmpegBinarySensor): diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index 06814d85f88..d8467a6cbfe 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -46,7 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value_template is not None: value_template.hass = hass - yield from async_add_devices([MqttBinarySensor( + async_add_devices([MqttBinarySensor( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS), diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 8f11424f54c..35666e0ea55 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -66,7 +66,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.error('No sensors added') return False - yield from async_add_devices(sensors, True) + async_add_devices(sensors, True) return True diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py index be41fd96556..c97ba17b874 100644 --- a/homeassistant/components/binary_sensor/threshold.py +++ b/homeassistant/components/binary_sensor/threshold.py @@ -52,7 +52,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): limit_type = config.get(CONF_TYPE) device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) - yield from async_add_devices( + async_add_devices( [ThresholdSensor(hass, entity_id, name, threshold, limit_type, device_class)], True) return True diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index 6b00ae240ed..ed8c84f90df 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -34,7 +34,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a FFmpeg Camera.""" if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)): return - yield from async_add_devices([FFmpegCamera(hass, config)]) + async_add_devices([FFmpegCamera(hass, config)]) class FFmpegCamera(Camera): diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index f9a4e8c2f06..3f50bc799c4 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a generic IP Camera.""" - yield from async_add_devices([GenericCamera(hass, config)]) + async_add_devices([GenericCamera(hass, config)]) class GenericCamera(Camera): diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 8d52785557b..fa46ea55e2c 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a MJPEG IP Camera.""" - yield from async_add_devices([MjpegCamera(hass, config)]) + async_add_devices([MjpegCamera(hass, config)]) def extract_image_from_mjpeg(stream): diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 39939c73d0d..c5d87c39086 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -153,7 +153,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): ) devices.append(device) - yield from async_add_devices(devices) + async_add_devices(devices) @asyncio.coroutine diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py index 12615262b26..5148ce8b245 100644 --- a/homeassistant/components/camera/zoneminder.py +++ b/homeassistant/components/camera/zoneminder.py @@ -75,4 +75,4 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.warning('No active cameras found') return - yield from async_add_devices(cameras) + async_add_devices(cameras) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index da746270197..d4b8ef16985 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -63,7 +63,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): min_cycle_duration = config.get(CONF_MIN_DUR) tolerance = config.get(CONF_TOLERANCE) - yield from async_add_devices([GenericThermostat( + async_add_devices([GenericThermostat( hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, tolerance)]) diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 97ddad74d79..6403e0bbc85 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value_template is not None: value_template.hass = hass - yield from async_add_devices([MqttCover( + async_add_devices([MqttCover( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 170159e1d25..e03cb72ea44 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -4,6 +4,7 @@ Sets up a demo environment that mimics interaction with devices. For more details about this component, please refer to the documentation https://home-assistant.io/components/demo/ """ +import asyncio import time import homeassistant.bootstrap as bootstrap @@ -34,7 +35,8 @@ COMPONENTS_WITH_DEMO_PLATFORM = [ ] -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Setup a demo environment.""" group = loader.get_component('group') configurator = loader.get_component('configurator') @@ -44,7 +46,7 @@ def setup(hass, config): config.setdefault(DOMAIN, {}) if config[DOMAIN].get('hide_demo_state') != 1: - hass.states.set('a.Demo_Mode', 'Enabled') + hass.states.async_set('a.Demo_Mode', 'Enabled') # Setup sun if not hass.config.latitude: @@ -53,50 +55,71 @@ def setup(hass, config): if not hass.config.longitude: hass.config.longitude = 117.22743 - bootstrap.setup_component(hass, 'sun') + tasks = [ + bootstrap.async_setup_component(hass, 'sun') + ] # Setup demo platforms demo_config = config.copy() for component in COMPONENTS_WITH_DEMO_PLATFORM: demo_config[component] = {CONF_PLATFORM: 'demo'} - bootstrap.setup_component(hass, component, demo_config) + tasks.append( + bootstrap.async_setup_component(hass, component, demo_config)) + + # Set up input select + tasks.append(bootstrap.async_setup_component( + hass, 'input_select', + {'input_select': + {'living_room_preset': {'options': ['Visitors', + 'Visitors with kids', + 'Home Alone']}, + 'who_cooks': {'icon': 'mdi:panda', + 'initial': 'Anne Therese', + 'name': 'Cook today', + 'options': ['Paulus', 'Anne Therese']}}})) + # Set up input boolean + tasks.append(bootstrap.async_setup_component( + hass, 'input_boolean', + {'input_boolean': {'notify': { + 'icon': 'mdi:car', + 'initial': False, + 'name': 'Notify Anne Therese is home'}}})) + + # Set up input boolean + tasks.append(bootstrap.async_setup_component( + hass, 'input_slider', + {'input_slider': { + 'noise_allowance': {'icon': 'mdi:bell-ring', + 'min': 0, + 'max': 10, + 'name': 'Allowed Noise', + 'unit_of_measurement': 'dB'}}})) + + # Set up weblink + tasks.append(bootstrap.async_setup_component( + hass, 'weblink', + {'weblink': {'entities': [{'name': 'Router', + 'url': 'http://192.168.1.1'}]}})) + + results = yield from asyncio.gather(*tasks, loop=hass.loop) + + if any(not result for result in results): + return False # Setup example persistent notification - persistent_notification.create( + persistent_notification.async_create( hass, 'This is an example of a persistent notification.', title='Example Notification') # Setup room groups - lights = sorted(hass.states.entity_ids('light')) - switches = sorted(hass.states.entity_ids('switch')) - media_players = sorted(hass.states.entity_ids('media_player')) + lights = sorted(hass.states.async_entity_ids('light')) + switches = sorted(hass.states.async_entity_ids('switch')) + media_players = sorted(hass.states.async_entity_ids('media_player')) - group.Group.create_group(hass, 'living room', [ - lights[1], switches[0], 'input_select.living_room_preset', - 'rollershutter.living_room_window', media_players[1], - 'scene.romantic_lights']) - group.Group.create_group(hass, 'bedroom', [ - lights[0], switches[1], media_players[0], - 'input_slider.noise_allowance']) - group.Group.create_group(hass, 'kitchen', [ - lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door']) - group.Group.create_group(hass, 'doors', [ - 'lock.front_door', 'lock.kitchen_door', - 'garage_door.right_garage_door', 'garage_door.left_garage_door']) - group.Group.create_group(hass, 'automations', [ - 'input_select.who_cooks', 'input_boolean.notify', ]) - group.Group.create_group(hass, 'people', [ - 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy', - 'device_tracker.demo_paulus']) - group.Group.create_group(hass, 'downstairs', [ - 'group.living_room', 'group.kitchen', - 'scene.romantic_lights', 'rollershutter.kitchen_window', - 'rollershutter.living_room_window', 'group.doors', - 'thermostat.ecobee', - ], view=True) + tasks2 = [] # Setup scripts - bootstrap.setup_component( + tasks2.append(bootstrap.async_setup_component( hass, 'script', {'script': { 'demo': { @@ -115,10 +138,10 @@ def setup(hass, config): 'service': 'light.turn_off', 'data': {ATTR_ENTITY_ID: lights[0]} }] - }}}) + }}})) # Setup scenes - bootstrap.setup_component( + tasks2.append(bootstrap.async_setup_component( hass, 'scene', {'scene': [ {'name': 'Romantic lights', @@ -132,41 +155,37 @@ def setup(hass, config): switches[0]: True, switches[1]: False, }}, - ]}) + ]})) - # Set up input select - bootstrap.setup_component( - hass, 'input_select', - {'input_select': - {'living_room_preset': {'options': ['Visitors', - 'Visitors with kids', - 'Home Alone']}, - 'who_cooks': {'icon': 'mdi:panda', - 'initial': 'Anne Therese', - 'name': 'Cook today', - 'options': ['Paulus', 'Anne Therese']}}}) - # Set up input boolean - bootstrap.setup_component( - hass, 'input_boolean', - {'input_boolean': {'notify': {'icon': 'mdi:car', - 'initial': False, - 'name': 'Notify Anne Therese is home'}}}) + tasks2.append(group.Group.async_create_group(hass, 'living room', [ + lights[1], switches[0], 'input_select.living_room_preset', + 'rollershutter.living_room_window', media_players[1], + 'scene.romantic_lights'])) + tasks2.append(group.Group.async_create_group(hass, 'bedroom', [ + lights[0], switches[1], media_players[0], + 'input_slider.noise_allowance'])) + tasks2.append(group.Group.async_create_group(hass, 'kitchen', [ + lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])) + tasks2.append(group.Group.async_create_group(hass, 'doors', [ + 'lock.front_door', 'lock.kitchen_door', + 'garage_door.right_garage_door', 'garage_door.left_garage_door'])) + tasks2.append(group.Group.async_create_group(hass, 'automations', [ + 'input_select.who_cooks', 'input_boolean.notify', ])) + tasks2.append(group.Group.async_create_group(hass, 'people', [ + 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy', + 'device_tracker.demo_paulus'])) + tasks2.append(group.Group.async_create_group(hass, 'downstairs', [ + 'group.living_room', 'group.kitchen', + 'scene.romantic_lights', 'rollershutter.kitchen_window', + 'rollershutter.living_room_window', 'group.doors', + 'thermostat.ecobee', + ], view=True)) - # Set up input boolean - bootstrap.setup_component( - hass, 'input_slider', - {'input_slider': { - 'noise_allowance': {'icon': 'mdi:bell-ring', - 'min': 0, - 'max': 10, - 'name': 'Allowed Noise', - 'unit_of_measurement': 'dB'}}}) + results = yield from asyncio.gather(*tasks2, loop=hass.loop) + + if any(not result for result in results): + return False - # Set up weblink - bootstrap.setup_component( - hass, 'weblink', - {'weblink': {'entities': [{'name': 'Router', - 'url': 'http://192.168.1.1'}]}}) # Setup configurator configurator_ids = [] @@ -184,14 +203,17 @@ def setup(hass, config): else: configurator.request_done(configurator_ids[0]) - request_id = configurator.request_config( - hass, "Philips Hue", hue_configuration_callback, - description=("Press the button on the bridge to register Philips Hue " - "with Home Assistant."), - description_image="/static/images/config_philips_hue.jpg", - submit_caption="I have pressed the button" - ) + def setup_configurator(): + """Setup configurator.""" + request_id = configurator.request_config( + hass, "Philips Hue", hue_configuration_callback, + description=("Press the button on the bridge to register Philips " + "Hue with Home Assistant."), + description_image="/static/images/config_philips_hue.jpg", + submit_caption="I have pressed the button" + ) + configurator_ids.append(request_id) - configurator_ids.append(request_id) + hass.async_add_job(setup_configurator) return True diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 5aa9765d983..c11e25ae130 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -14,12 +14,11 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.bootstrap import ( - async_prepare_setup_platform, async_log_exception) +from homeassistant.bootstrap import async_prepare_setup_platform from homeassistant.core import callback from homeassistant.components import group, zone from homeassistant.components.discovery import SERVICE_NETGEAR -from homeassistant.config import load_yaml_config_file +from homeassistant.config import load_yaml_config_file, async_log_exception from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_per_platform, discovery diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py index 3463cc01bbc..968f666fa72 100644 --- a/homeassistant/components/fan/mqtt.py +++ b/homeassistant/components/fan/mqtt.py @@ -78,7 +78,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup MQTT fan platform.""" - yield from async_add_devices([MqttFan( + async_add_devices([MqttFan( config.get(CONF_NAME), { key: config.get(key) for key in ( diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py index 43c5c9dd7f0..bb1a7accd15 100644 --- a/homeassistant/components/image_processing/microsoft_face_detect.py +++ b/homeassistant/components/image_processing/microsoft_face_detect.py @@ -60,7 +60,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME) )) - yield from async_add_devices(entities) + async_add_devices(entities) class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity): diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py index 97d210d584a..ec4549dfe0c 100644 --- a/homeassistant/components/image_processing/microsoft_face_identify.py +++ b/homeassistant/components/image_processing/microsoft_face_identify.py @@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera.get(CONF_NAME) )) - yield from async_add_devices(entities) + async_add_devices(entities) class ImageProcessingFaceEntity(ImageProcessingEntity): diff --git a/homeassistant/components/image_processing/openalpr_cloud.py b/homeassistant/components/image_processing/openalpr_cloud.py index 7c7d26ce724..7f8bd83116c 100644 --- a/homeassistant/components/image_processing/openalpr_cloud.py +++ b/homeassistant/components/image_processing/openalpr_cloud.py @@ -66,7 +66,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera[CONF_ENTITY_ID], params, confidence, camera.get(CONF_NAME) )) - yield from async_add_devices(entities) + async_add_devices(entities) class OpenAlprCloudEntity(ImageProcessingAlprEntity): diff --git a/homeassistant/components/image_processing/openalpr_local.py b/homeassistant/components/image_processing/openalpr_local.py index a9378dd653d..4040efe3bf4 100644 --- a/homeassistant/components/image_processing/openalpr_local.py +++ b/homeassistant/components/image_processing/openalpr_local.py @@ -70,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera[CONF_ENTITY_ID], command, confidence, camera.get(CONF_NAME) )) - yield from async_add_devices(entities) + async_add_devices(entities) class ImageProcessingAlprEntity(ImageProcessingEntity): diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 77b804cb499..3110c2091ad 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -70,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.setdefault( CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE)) - yield from async_add_devices([MqttLight( + async_add_devices([MqttLight( config.get(CONF_NAME), { key: config.get(key) for key in ( diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index abc05198443..b9fb6c54cb4 100755 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -61,7 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a MQTT JSON Light.""" - yield from async_add_devices([MqttJson( + async_add_devices([MqttJson( config.get(CONF_NAME), { key: config.get(key) for key in ( diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt_template.py index 931b5f68ab3..2f240ec12a6 100755 --- a/homeassistant/components/light/mqtt_template.py +++ b/homeassistant/components/light/mqtt_template.py @@ -64,7 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a MQTT Template light.""" - yield from async_add_devices([MqttTemplate( + async_add_devices([MqttTemplate( hass, config.get(CONF_NAME), config.get(CONF_EFFECT_LIST), diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/light/rflink.py index 82b7b46b1f8..4d49186398a 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/light/rflink.py @@ -117,7 +117,7 @@ def devices_from_config(domain_config, hass=None): @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Rflink light platform.""" - yield from async_add_devices(devices_from_config(config, hass)) + async_add_devices(devices_from_config(config, hass)) # Add new (unconfigured) devices to user desired group if config[CONF_NEW_DEVICES_GROUP]: @@ -136,7 +136,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): device_config = config[CONF_DEVICE_DEFAULTS] device = entity_class(device_id, hass, **device_config) - yield from async_add_devices([device]) + async_add_devices([device]) # Register entity to listen to incoming Rflink events hass.data[DATA_ENTITY_LOOKUP][ @@ -156,7 +156,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class RflinkLight(SwitchableRflinkDevice, Light): """Representation of a Rflink light.""" - pass + @property + def entity_id(self): + """Return entity id.""" + return "light.{}".format(self.name) class DimmableRflinkLight(SwitchableRflinkDevice, Light): @@ -164,6 +167,11 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light): _brightness = 255 + @property + def entity_id(self): + """Return entity id.""" + return "light.{}".format(self.name) + @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the device on.""" @@ -202,6 +210,11 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light): _brightness = 255 + @property + def entity_id(self): + """Return entity id.""" + return "light.{}".format(self.name) + @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the device on and set dim level.""" diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py index 00540f66150..43d5788af9b 100644 --- a/homeassistant/components/lock/mqtt.py +++ b/homeassistant/components/lock/mqtt.py @@ -47,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value_template is not None: value_template.hass = hass - yield from async_add_devices([MqttLock( + async_add_devices([MqttLock( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py index 01b4b32deb2..e6fd4e286ab 100644 --- a/homeassistant/components/media_player/anthemav.py +++ b/homeassistant/components/media_player/anthemav.py @@ -63,7 +63,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.debug('dump_rawdata: '+avr.protocol.dump_rawdata) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.avr.close) - yield from async_add_devices([device]) + async_add_devices([device]) class AnthemAVR(MediaPlayerDevice): diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index efab17a61a9..a18bb10e75d 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -85,7 +85,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): return False players = yield from lms.create_players() - yield from async_add_devices(players) + async_add_devices(players) return True diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index aea10e3c44d..b5f88eb28a4 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -63,7 +63,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config[CONF_ATTRS] ) - yield from async_add_devices([player]) + async_add_devices([player]) def validate_config(config): diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 7e20338f4ab..1abe6432409 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -18,7 +18,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent DOMAIN = 'scene' -DEPENDENCIES = ['group'] STATE = 'scening' CONF_ENTITIES = "entities" diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py index c7365ea65d9..2081dfe89ab 100644 --- a/homeassistant/components/scene/homeassistant.py +++ b/homeassistant/components/scene/homeassistant.py @@ -13,7 +13,6 @@ from homeassistant.const import ( from homeassistant.core import State from homeassistant.helpers.state import async_reproduce_state -DEPENDENCIES = ['group'] STATE = 'scening' CONF_ENTITIES = "entities" @@ -29,7 +28,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if not isinstance(scene_config, list): scene_config = [scene_config] - yield from async_add_devices(HomeAssistantScene( + async_add_devices(HomeAssistantScene( hass, _process_config(scene)) for scene in scene_config) return True diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 1cca7c8d790..cf4843353b5 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -25,7 +25,6 @@ from homeassistant.helpers.script import Script DOMAIN = "script" ENTITY_ID_FORMAT = DOMAIN + '.{}' GROUP_NAME_ALL_SCRIPTS = 'all scripts' -DEPENDENCIES = ["group"] CONF_SEQUENCE = "sequence" @@ -130,6 +129,7 @@ def async_setup(hass, config): schema=SCRIPT_TURN_ONOFF_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_TOGGLE, toggle_service, schema=SCRIPT_TURN_ONOFF_SCHEMA) + return True diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py index 15cfc200c4d..e1d6c775196 100644 --- a/homeassistant/components/sensor/api_streams.py +++ b/homeassistant/components/sensor/api_streams.py @@ -61,7 +61,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, remove_logger) - yield from async_add_devices([entity]) + async_add_devices([entity]) class APICount(Entity): diff --git a/homeassistant/components/sensor/dnsip.py b/homeassistant/components/sensor/dnsip.py index 2807dbc2c58..67b2e04d157 100644 --- a/homeassistant/components/sensor/dnsip.py +++ b/homeassistant/components/sensor/dnsip.py @@ -49,7 +49,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): else: resolver = config.get(CONF_RESOLVER) - yield from async_add_devices([WanIpSensor( + async_add_devices([WanIpSensor( hass, hostname, resolver, ipv6)], True) diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py index 729b435edbc..04fe1e97964 100644 --- a/homeassistant/components/sensor/dsmr.py +++ b/homeassistant/components/sensor/dsmr.py @@ -103,7 +103,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): DerivativeDSMREntity('Hourly Gas Consumption', gas_obis), ] - yield from async_add_devices(devices) + async_add_devices(devices) def update_entities_telegram(telegram): """Update entities with latests telegram & trigger state update.""" diff --git a/homeassistant/components/sensor/envisalink.py b/homeassistant/components/sensor/envisalink.py index 20142c13c3b..1a870114d65 100644 --- a/homeassistant/components/sensor/envisalink.py +++ b/homeassistant/components/sensor/envisalink.py @@ -34,7 +34,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DATA_EVL]) devices.append(device) - yield from async_add_devices(devices) + async_add_devices(devices) class EnvisalinkSensor(EnvisalinkDevice, Entity): diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/sensor/min_max.py index c1eb57170f4..d612ca5cf26 100644 --- a/homeassistant/components/sensor/min_max.py +++ b/homeassistant/components/sensor/min_max.py @@ -61,7 +61,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): sensor_type = config.get(CONF_TYPE) round_digits = config.get(CONF_ROUND_DIGITS) - yield from async_add_devices( + async_add_devices( [MinMaxSensor(hass, entity_ids, name, sensor_type, round_digits)], True) return True diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/sensor/moon.py index 2de5b613065..71995533b7b 100644 --- a/homeassistant/components/sensor/moon.py +++ b/homeassistant/components/sensor/moon.py @@ -33,7 +33,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Moon sensor.""" name = config.get(CONF_NAME) - yield from async_add_devices([MoonSensor(name)], True) + async_add_devices([MoonSensor(name)], True) return True diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index a811d4e691c..a5ecd029a88 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -38,7 +38,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value_template is not None: value_template.hass = hass - yield from async_add_devices([MqttSensor( + async_add_devices([MqttSensor( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_QOS), diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index ad615b5c890..432fff67802 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -59,7 +59,7 @@ MQTT_PAYLOAD = vol.Schema(vol.All(json.loads, vol.Schema({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup MQTT Sensor.""" - yield from async_add_devices([MQTTRoomSensor( + async_add_devices([MQTTRoomSensor( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_DEVICE_ID), diff --git a/homeassistant/components/sensor/random.py b/homeassistant/components/sensor/random.py index a495c4ddb8b..21251ab5f3b 100644 --- a/homeassistant/components/sensor/random.py +++ b/homeassistant/components/sensor/random.py @@ -36,7 +36,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): minimum = config.get(CONF_MINIMUM) maximum = config.get(CONF_MAXIMUM) - yield from async_add_devices([RandomSensor(name, minimum, maximum)], True) + async_add_devices([RandomSensor(name, minimum, maximum)], True) return True diff --git a/homeassistant/components/sensor/rflink.py b/homeassistant/components/sensor/rflink.py index eec21e161c1..575b2daf674 100644 --- a/homeassistant/components/sensor/rflink.py +++ b/homeassistant/components/sensor/rflink.py @@ -74,7 +74,7 @@ def devices_from_config(domain_config, hass=None): @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Rflink platform.""" - yield from async_add_devices(devices_from_config(config, hass)) + async_add_devices(devices_from_config(config, hass)) # Add new (unconfigured) devices to user desired group if config[CONF_NEW_DEVICES_GROUP]: @@ -91,7 +91,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): rflinksensor = partial(RflinkSensor, device_id, hass) device = rflinksensor(event[EVENT_KEY_SENSOR], event[EVENT_KEY_UNIT]) # Add device entity - yield from async_add_devices([device]) + async_add_devices([device]) # Register entity to listen to incoming rflink events hass.data[DATA_ENTITY_LOOKUP][ @@ -122,6 +122,11 @@ class RflinkSensor(RflinkDevice): """Domain specific event handler.""" self._state = event['value'] + @property + def entity_id(self): + """Return entity id.""" + return "sensor.{}".format(self.name) + @property def unit_of_measurement(self): """Return measurement unit.""" diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py index ff2df5ef893..342724830e3 100644 --- a/homeassistant/components/sensor/statistics.py +++ b/homeassistant/components/sensor/statistics.py @@ -50,7 +50,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): name = config.get(CONF_NAME) sampling_size = config.get(CONF_SAMPLING_SIZE) - yield from async_add_devices( + async_add_devices( [StatisticsSensor(hass, entity_id, name, sampling_size)], True) return True diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index aba42519e60..42481c95510 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -69,7 +69,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.error("No sensors added") return False - yield from async_add_devices(sensors, True) + async_add_devices(sensors, True) return True diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py index 04bd8a5aa0f..9182145dc95 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/sensor/time_date.py @@ -46,7 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): for variable in config[CONF_DISPLAY_OPTIONS]: devices.append(TimeDateSensor(variable)) - yield from async_add_devices(devices, True) + async_add_devices(devices, True) return True diff --git a/homeassistant/components/sensor/worldclock.py b/homeassistant/components/sensor/worldclock.py index bce4895e408..7f1e6429ba5 100644 --- a/homeassistant/components/sensor/worldclock.py +++ b/homeassistant/components/sensor/worldclock.py @@ -35,7 +35,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): name = config.get(CONF_NAME) time_zone = dt_util.get_time_zone(config.get(CONF_TIME_ZONE)) - yield from async_add_devices([WorldClockSensor(time_zone, name)], True) + async_add_devices([WorldClockSensor(time_zone, name)], True) return True diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index f5541f1bef2..047edd0b994 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -78,7 +78,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): dev = [] for sensor_type in config[CONF_MONITORED_CONDITIONS]: dev.append(YrSensor(sensor_type)) - yield from async_add_devices(dev) + async_add_devices(dev) weather = YrData(hass, coordinates, dev) # Update weather on the hour, spread seconds diff --git a/homeassistant/components/switch/hook.py b/homeassistant/components/switch/hook.py index 689ab675b5f..a21d9814768 100644 --- a/homeassistant/components/switch/hook.py +++ b/homeassistant/components/switch/hook.py @@ -74,7 +74,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if response is not None: yield from response.release() - yield from async_add_devices( + async_add_devices( HookSmartHome( hass, token, diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index d0f2524e3de..d94815a1d2e 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -43,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if value_template is not None: value_template.hass = hass - yield from async_add_devices([MqttSwitch( + async_add_devices([MqttSwitch( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/switch/rest.py index cfa11897de9..74add400850 100644 --- a/homeassistant/components/switch/rest.py +++ b/homeassistant/components/switch/rest.py @@ -72,7 +72,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if req is not None: yield from req.release() - yield from async_add_devices( + async_add_devices( [RestSwitch(hass, name, resource, body_on, body_off, is_on_template, timeout)]) diff --git a/homeassistant/components/switch/rflink.py b/homeassistant/components/switch/rflink.py index 737554154c2..1abeb3eeada 100644 --- a/homeassistant/components/switch/rflink.py +++ b/homeassistant/components/switch/rflink.py @@ -52,7 +52,7 @@ def devices_from_config(domain_config, hass=None): @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Rflink platform.""" - yield from async_add_devices(devices_from_config(config, hass)) + async_add_devices(devices_from_config(config, hass)) class RflinkSwitch(SwitchableRflinkDevice, SwitchDevice): diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index f17d95b21b3..91ac16fe06c 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -70,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.error("No switches added") return False - yield from async_add_devices(switches, True) + async_add_devices(switches, True) return True diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index c18a87710fe..f05fb2a9ae5 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -276,7 +276,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): device = hass.data[DATA_ZWAVE_DICT].pop( discovery_info[const.DISCOVERY_DEVICE]) if device: - yield from async_add_devices([device]) + async_add_devices([device]) return True else: return False diff --git a/homeassistant/config.py b/homeassistant/config.py index 852151e83f5..388093ec37a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -10,23 +10,27 @@ import sys from typing import Any, List, Tuple # NOQA import voluptuous as vol +from voluptuous.humanize import humanize_error from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM, CONF_TIME_ZONE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS, __version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB) -from homeassistant.core import DOMAIN as CONF_CORE +from homeassistant.core import callback, DOMAIN as CONF_CORE from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import get_component +from homeassistant.loader import get_component, get_platform from homeassistant.util.yaml import load_yaml import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as date_util, 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 _LOGGER = logging.getLogger(__name__) +DATA_PERSISTENT_ERRORS = 'bootstrap_persistent_errors' +HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)' YAML_CONFIG_FILE = 'configuration.yaml' VERSION_FILE = '.HA_VERSION' CONFIG_DIR_NAME = '.homeassistant' @@ -274,6 +278,35 @@ def process_ha_config_upgrade(hass): outp.write(__version__) +@callback +def async_log_exception(ex, domain, config, hass): + """Generate log exception for config validation. + + This method must be run in the event loop. + """ + message = 'Invalid config for [{}]: '.format(domain) + if hass is not None: + async_notify_setup_error(hass, domain, True) + + if 'extra keys not allowed' in ex.error_message: + message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ + .format(ex.path[-1], domain, domain, + '->'.join(str(m) for m in ex.path)) + else: + message += '{}.'.format(humanize_error(config, ex)) + + domain_config = config.get(domain, config) + message += " (See {}, line {}). ".format( + getattr(domain_config, '__config_file__', '?'), + getattr(domain_config, '__line__', '?')) + + if domain != 'homeassistant': + message += ('Please check the docs at ' + 'https://home-assistant.io/components/{}/'.format(domain)) + + _LOGGER.error(message) + + @asyncio.coroutine def async_process_ha_core_config(hass, config): """Process the [homeassistant] section from the config. @@ -483,6 +516,67 @@ def merge_packages_config(config, packages): return config +@callback +def async_process_component_config(hass, config, domain): + """Check component config and return processed config. + + Raise a vol.Invalid exception on error. + + This method must be run in the event loop. + """ + component = get_component(domain) + + if hasattr(component, 'CONFIG_SCHEMA'): + try: + config = component.CONFIG_SCHEMA(config) + except vol.Invalid as ex: + async_log_exception(ex, domain, config, hass) + return None + + elif hasattr(component, 'PLATFORM_SCHEMA'): + platforms = [] + for p_name, p_config in config_per_platform(config, domain): + # Validate component specific platform schema + try: + p_validated = component.PLATFORM_SCHEMA(p_config) + except vol.Invalid as ex: + async_log_exception(ex, domain, config, hass) + continue + + # Not all platform components follow same pattern for platforms + # So if p_name is None we are not going to validate platform + # (the automation component is one of them) + if p_name is None: + platforms.append(p_validated) + continue + + platform = get_platform(domain, p_name) + + if platform is None: + continue + + # Validate platform specific schema + if hasattr(platform, 'PLATFORM_SCHEMA'): + # pylint: disable=no-member + try: + p_validated = platform.PLATFORM_SCHEMA(p_validated) + except vol.Invalid as ex: + async_log_exception(ex, '{}.{}'.format(domain, p_name), + p_validated, hass) + continue + + platforms.append(p_validated) + + # Create a copy of the configuration with all config for current + # component removed and add validated config back in. + filter_keys = extract_domain_configs(config, domain) + config = {key: value for key, value in config.items() + if key not in filter_keys} + config[domain] = platforms + + return config + + @asyncio.coroutine def async_check_ha_config_file(hass): """Check if HA config file valid. @@ -501,3 +595,25 @@ def async_check_ha_config_file(hass): return None return re.sub(r'\033\[[^m]*m', '', str(stdout_data, 'utf-8')) + + +@callback +def async_notify_setup_error(hass, component, link=False): + """Print a persistent notification. + + This method must be run in the event loop. + """ + from homeassistant.components import persistent_notification + + errors = hass.data.get(DATA_PERSISTENT_ERRORS) + + if errors is None: + errors = hass.data[DATA_PERSISTENT_ERRORS] = {} + + errors[component] = errors.get(component) or link + _lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name) + if link else name for name, link in errors.items()] + message = ('The following components and platforms could not be set up:\n' + '* ' + '\n* '.join(list(_lst)) + '\nPlease check your config') + persistent_notification.async_create( + hass, message, 'Invalid config', 'invalid_config') diff --git a/homeassistant/core.py b/homeassistant/core.py index 29c61842c67..90212e86c3b 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -122,6 +122,7 @@ class HomeAssistant(object): self.loop.set_default_executor(self.executor) self.loop.set_exception_handler(async_loop_exception_handler) self._pending_tasks = [] + self._track_task = False self.bus = EventBus(self) self.services = ServiceRegistry(self) self.states = StateMachine(self.bus, self.loop) @@ -178,28 +179,7 @@ class HomeAssistant(object): self.loop.call_soon_threadsafe(self.async_add_job, target, *args) @callback - def _async_add_job(self, target: Callable[..., None], *args: Any) -> None: - """Add a job from within the eventloop. - - This method must be run in the event loop. - - target: target to call. - args: parameters for method to call. - """ - if asyncio.iscoroutine(target): - self.loop.create_task(target) - elif is_callback(target): - self.loop.call_soon(target, *args) - elif asyncio.iscoroutinefunction(target): - self.loop.create_task(target(*args)) - else: - self.loop.run_in_executor(None, target, *args) - - async_add_job = _async_add_job - - @callback - def _async_add_job_tracking(self, target: Callable[..., None], - *args: Any) -> None: + def async_add_job(self, target: Callable[..., None], *args: Any) -> None: """Add a job from within the eventloop. This method must be run in the event loop. @@ -219,19 +199,21 @@ class HomeAssistant(object): task = self.loop.run_in_executor(None, target, *args) # if a task is sheduled - if task is not None: + if self._track_task and task is not None: self._pending_tasks.append(task) + return task + @callback def async_track_tasks(self): """Track tasks so you can wait for all tasks to be done.""" - self.async_add_job = self._async_add_job_tracking + self._track_task = True @asyncio.coroutine def async_stop_track_tasks(self): """Track tasks so you can wait for all tasks to be done.""" yield from self.async_block_till_done() - self.async_add_job = self._async_add_job + self._track_task = False @callback def async_run_job(self, target: Callable[..., None], *args: Any) -> None: diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index db17b8926c1..5615f3a3199 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -63,20 +63,8 @@ def async_discover(hass, service, discovered=None, component=None, 'Cannot discover the {} component.'.format(component)) if component is not None and component not in hass.config.components: - did_lock = False - setup_lock = hass.data.get('setup_lock') - if setup_lock and setup_lock.locked(): - did_lock = True - yield from setup_lock.acquire() - - try: - # Could have been loaded while waiting for lock. - if component not in hass.config.components: - yield from bootstrap.async_setup_component(hass, component, - hass_config) - finally: - if did_lock: - setup_lock.release() + yield from bootstrap.async_setup_component( + hass, component, hass_config) data = { ATTR_SERVICE: service @@ -160,22 +148,11 @@ def async_load_platform(hass, component, platform, discovered=None, raise HomeAssistantError( 'Cannot discover the {} component.'.format(component)) - did_lock = False - setup_lock = hass.data.get('setup_lock') - if setup_lock and setup_lock.locked(): - did_lock = True - yield from setup_lock.acquire() - setup_success = True - try: - # Could have been loaded while waiting for lock. - if component not in hass.config.components: - setup_success = yield from bootstrap.async_setup_component( - hass, component, hass_config) - finally: - if did_lock: - setup_lock.release() + if component not in hass.config.components: + setup_success = yield from bootstrap.async_setup_component( + hass, component, hass_config) # No need to fire event if we could not setup component if not setup_success: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index ad88045039f..1b20695b349 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -3,8 +3,7 @@ import asyncio from datetime import timedelta from homeassistant import config as conf_util -from homeassistant.bootstrap import ( - async_prepare_setup_platform, async_prepare_setup_component) +from homeassistant.bootstrap import async_prepare_setup_platform from homeassistant.const import ( ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, DEVICE_DEFAULT_NAME) @@ -49,12 +48,9 @@ class EntityComponent(object): def setup(self, config): """Set up a full entity component. - Loads the platforms from the config and will listen for supported - discovered platforms. + This doesn't block the executor to protect from deadlocks. """ - run_coroutine_threadsafe( - self.async_setup(config), self.hass.loop - ).result() + self.hass.add_job(self.async_setup(config)) @asyncio.coroutine def async_setup(self, config): @@ -143,14 +139,16 @@ class EntityComponent(object): if getattr(platform, 'async_setup_platform', None): yield from platform.async_setup_platform( self.hass, platform_config, - entity_platform.async_add_entities, discovery_info + entity_platform.async_schedule_add_entities, discovery_info ) else: yield from self.hass.loop.run_in_executor( None, platform.setup_platform, self.hass, platform_config, - entity_platform.add_entities, discovery_info + entity_platform.schedule_add_entities, discovery_info ) + yield from entity_platform.async_block_entities_done() + self.hass.config.components.add( '{}.{}'.format(self.domain, platform_type)) except Exception: # pylint: disable=broad-except @@ -275,7 +273,7 @@ class EntityComponent(object): self.logger.error(err) return None - conf = yield from async_prepare_setup_component( + conf = conf_util.async_process_component_config( self.hass, conf, self.domain) if conf is None: @@ -295,9 +293,40 @@ class EntityPlatform(object): self.scan_interval = scan_interval self.entity_namespace = entity_namespace self.platform_entities = [] + self._tasks = [] self._async_unsub_polling = None self._process_updates = asyncio.Lock(loop=component.hass.loop) + @asyncio.coroutine + def async_block_entities_done(self): + """Wait until all entities add to hass.""" + if self._tasks: + pending = [task for task in self._tasks if not task.done()] + self._tasks.clear() + + if pending: + yield from asyncio.wait(pending, loop=self.component.hass.loop) + + def schedule_add_entities(self, new_entities, update_before_add=False): + """Add entities for a single platform.""" + if update_before_add: + for entity in new_entities: + entity.update() + + run_callback_threadsafe( + self.component.hass.loop, + self.async_schedule_add_entities, list(new_entities), False + ).result() + + @callback + def async_schedule_add_entities(self, new_entities, + update_before_add=False): + """Add entities for a single platform async.""" + self._tasks.append(self.component.hass.async_add_job( + self.async_add_entities( + new_entities, update_before_add=update_before_add) + )) + def add_entities(self, new_entities, update_before_add=False): """Add entities for a single platform.""" if update_before_add: @@ -306,8 +335,7 @@ class EntityPlatform(object): run_coroutine_threadsafe( self.async_add_entities(list(new_entities), False), - self.component.hass.loop - ).result() + self.component.hass.loop).result() @asyncio.coroutine def async_add_entities(self, new_entities, update_before_add=False): @@ -319,8 +347,16 @@ class EntityPlatform(object): if not new_entities: return - tasks = [self._async_process_entity(entity, update_before_add) - for entity in new_entities] + @asyncio.coroutine + def async_process_entity(new_entity): + """Add entities to StateMachine.""" + ret = yield from self.component.async_add_entity( + new_entity, self, update_before_add=update_before_add + ) + if ret: + self.platform_entities.append(new_entity) + + tasks = [async_process_entity(entity) for entity in new_entities] yield from asyncio.wait(tasks, loop=self.component.hass.loop) yield from self.component.async_update_group() @@ -334,15 +370,6 @@ class EntityPlatform(object): self.component.hass, self._update_entity_states, self.scan_interval ) - @asyncio.coroutine - def _async_process_entity(self, new_entity, update_before_add): - """Add entities to StateMachine.""" - ret = yield from self.component.async_add_entity( - new_entity, self, update_before_add=update_before_add - ) - if ret: - self.platform_entities.append(new_entity) - @asyncio.coroutine def async_reset(self): """Remove all entities and reset data. diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 60ba924f46c..a24f89c0e3f 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -170,41 +170,6 @@ def get_component(comp_name) -> Optional[ModuleType]: return None -def load_order_components(components: Sequence[str]) -> OrderedSet: - """Take in a list of components we want to load. - - - filters out components we cannot load - - filters out components that have invalid/circular dependencies - - Will make sure the recorder component is loaded first - - Will ensure that all components that do not directly depend on - the group component will be loaded before the group component. - - returns an OrderedSet load order. - - Makes sure MQTT eventstream is available for publish before - components start updating states. - - Async friendly. - """ - _check_prepared() - - load_order = OrderedSet() - - # Sort the list of modules on if they depend on group component or not. - # Components that do not depend on the group usually set up states. - # Components that depend on group usually use states in their setup. - for comp_load_order in sorted((load_order_component(component) - for component in components), - key=lambda order: 'group' in order): - load_order.update(comp_load_order) - - # Push some to first place in load order - for comp in ('mqtt_eventstream', 'mqtt', 'recorder', - 'introduction', 'logger'): - if comp in load_order: - load_order.promote(comp) - - return load_order - - def load_order_component(comp_name: str) -> OrderedSet: """Return an OrderedSet of components in the correct order of loading. diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 154754c667a..38138c87883 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -26,8 +26,8 @@ MOCKS = { 'load*': ("homeassistant.config.load_yaml", yaml.load_yaml), 'get': ("homeassistant.loader.get_component", loader.get_component), 'secrets': ("homeassistant.util.yaml._secret_yaml", yaml._secret_yaml), - 'except': ("homeassistant.bootstrap.async_log_exception", - bootstrap.async_log_exception), + 'except': ("homeassistant.config.async_log_exception", + config_util.async_log_exception), 'package_error': ("homeassistant.config._log_pkg_error", config_util._log_pkg_error), } @@ -211,7 +211,7 @@ def check(config_path): def mock_except(ex, domain, config, # pylint: disable=unused-variable hass=None): - """Mock bootstrap.log_exception.""" + """Mock config.log_exception.""" MOCKS['except'][1](ex, domain, config, hass) res['except'][domain] = config.get(domain, config) diff --git a/tests/common.py b/tests/common.py index 55d6896d410..a1635e3387c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -12,8 +12,8 @@ from contextlib import contextmanager from aiohttp import web from homeassistant import core as ha, loader -from homeassistant.bootstrap import ( - setup_component, async_prepare_setup_component) +from homeassistant.bootstrap import setup_component, DATA_SETUP +from homeassistant.config import async_process_component_config from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE @@ -93,13 +93,16 @@ def async_test_home_assistant(loop): hass = ha.HomeAssistant(loop) + orig_async_add_job = hass.async_add_job + def async_add_job(target, *args): """Add a magic mock.""" if isinstance(target, MagicMock): return - hass._async_add_job_tracking(target, *args) + return orig_async_add_job(target, *args) hass.async_add_job = async_add_job + hass.async_track_tasks() hass.config.location_name = 'test home' hass.config.config_dir = get_test_config_dir() @@ -230,7 +233,7 @@ def mock_state_change_event(hass, new_state, old_state=None): def mock_http_component(hass, api_password=None): """Mock the HTTP component.""" hass.http = MagicMock(api_password=api_password) - hass.config.components.add('http') + mock_component(hass, 'http') hass.http.views = {} def mock_register_view(view): @@ -268,6 +271,19 @@ def mock_mqtt_component(hass): return mock_mqtt +def mock_component(hass, component): + """Mock a component is setup.""" + setup_tasks = hass.data.get(DATA_SETUP) + if setup_tasks is None: + setup_tasks = hass.data[DATA_SETUP] = {} + + if component not in setup_tasks: + AssertionError("Component {} is already setup".format(component)) + + hass.config.components.add(component) + setup_tasks[component] = asyncio.Task(mock_coro(True), loop=hass.loop) + + class MockModule(object): """Representation of a fake module.""" @@ -439,10 +455,10 @@ def assert_setup_component(count, domain=None): """ config = {} - @asyncio.coroutine + @ha.callback def mock_psc(hass, config_input, domain): """Mock the prepare_setup_component to capture config.""" - res = yield from async_prepare_setup_component( + res = async_process_component_config( hass, config_input, domain) config[domain] = None if res is None else res.get(domain) _LOGGER.debug('Configuration for %s, Validated: %s, Original %s', @@ -450,7 +466,7 @@ def assert_setup_component(count, domain=None): return res assert isinstance(config, dict) - with patch('homeassistant.bootstrap.async_prepare_setup_component', + with patch('homeassistant.config.async_process_component_config', mock_psc): yield config diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index f1bbb711848..2fe9e05d9d5 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -30,7 +30,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_fail_setup_without_state_topic(self): """Test for failing with no state topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(0) as config: assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { @@ -42,7 +41,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_fail_setup_without_command_topic(self): """Test failing with no command topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(0): assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { @@ -53,7 +51,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_update_state_via_state_topic(self): """Test updating with via state topic.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -77,7 +74,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_ignore_update_state_if_unknown_via_state_topic(self): """Test ignoring updates via state topic.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -98,7 +94,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_home_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -115,7 +110,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_home_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -133,7 +127,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_away_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -150,7 +143,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_arm_away_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -168,7 +160,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_disarm_publishes_mqtt(self): """Test publishing of MQTT messages while disarmed.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', @@ -185,7 +176,6 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): def test_disarm_not_publishes_mqtt_with_invalid_code(self): """Test not publishing of MQTT messages with invalid code.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, alarm_control_panel.DOMAIN, { alarm_control_panel.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 18e112fc498..c032c72446a 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component # pylint: disable=invalid-name @@ -15,7 +15,7 @@ class TestAutomationEvent(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.calls = [] @callback diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index ca8eef4fc0d..fa7658f3407 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -11,7 +11,7 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant, assert_setup_component, \ - fire_time_changed + fire_time_changed, mock_component # pylint: disable=invalid-name @@ -21,7 +21,7 @@ class TestAutomation(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.calls = [] @callback diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index a2746728fe3..df8baced090 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -5,7 +5,8 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation from tests.common import ( - mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) + mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, + mock_component) # pylint: disable=invalid-name @@ -15,7 +16,7 @@ class TestAutomationMQTT(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') mock_mqtt_component(self.hass) self.calls = [] diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 85842ccf5eb..8862303da5f 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component # pylint: disable=invalid-name @@ -15,7 +15,7 @@ class TestAutomationNumericState(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.calls = [] @callback diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 00048f1f577..f375aec4666 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -10,7 +10,8 @@ import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation from tests.common import ( - fire_time_changed, get_test_home_assistant, assert_setup_component) + fire_time_changed, get_test_home_assistant, assert_setup_component, + mock_component) # pylint: disable=invalid-name @@ -20,7 +21,7 @@ class TestAutomationState(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.hass.states.set('test.entity', 'hello') self.calls = [] diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index bad592a740a..47bbf6b680c 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -10,7 +10,8 @@ from homeassistant.components import sun import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util -from tests.common import fire_time_changed, get_test_home_assistant +from tests.common import ( + fire_time_changed, get_test_home_assistant, mock_component) # pylint: disable=invalid-name @@ -20,8 +21,8 @@ class TestAutomationSun(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') - self.hass.config.components.add('sun') + mock_component(self.hass, 'group') + mock_component(self.hass, 'sun') self.calls = [] diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 1971fb26d31..8bdf9f8f439 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -5,7 +5,8 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import ( + get_test_home_assistant, assert_setup_component, mock_component) # pylint: disable=invalid-name @@ -15,7 +16,7 @@ class TestAutomationTemplate(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.hass.states.set('test.entity', 'hello') self.calls = [] diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 8f323dd4b37..6a76bb887b8 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -9,7 +9,8 @@ import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation from tests.common import ( - fire_time_changed, get_test_home_assistant, assert_setup_component) + fire_time_changed, get_test_home_assistant, assert_setup_component, + mock_component) # pylint: disable=invalid-name @@ -19,7 +20,7 @@ class TestAutomationTime(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') self.calls = [] @callback diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index f2b304070b4..ea216b12a26 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component from homeassistant.components import automation, zone -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component # pylint: disable=invalid-name @@ -15,7 +15,7 @@ class TestAutomationZone(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') assert setup_component(self.hass, zone.DOMAIN, { 'zone': { 'name': 'test', diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py index f9630ae4b25..1b756f72f61 100644 --- a/tests/components/binary_sensor/test_mqtt.py +++ b/tests/components/binary_sensor/test_mqtt.py @@ -3,10 +3,10 @@ import unittest from homeassistant.bootstrap import setup_component import homeassistant.components.binary_sensor as binary_sensor -from tests.common import mock_mqtt_component, fire_mqtt_message from homeassistant.const import (STATE_OFF, STATE_ON) -from tests.common import get_test_home_assistant +from tests.common import ( + get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) class TestSensorMQTT(unittest.TestCase): @@ -23,7 +23,6 @@ class TestSensorMQTT(unittest.TestCase): def test_setting_sensor_value_via_mqtt_message(self): """Test the setting of the value via MQTT.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', @@ -49,7 +48,6 @@ class TestSensorMQTT(unittest.TestCase): def test_valid_device_class(self): """Test the setting of a valid sensor class.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', @@ -64,7 +62,6 @@ class TestSensorMQTT(unittest.TestCase): def test_invalid_device_class(self): """Test the setting of an invalid sensor class.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index c5b8b6a9f78..cd11321baa4 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -9,7 +9,7 @@ from uvcclient import nvr from homeassistant.bootstrap import setup_component from homeassistant.components.camera import uvc -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_http_component class TestUVCSetup(unittest.TestCase): @@ -18,8 +18,7 @@ class TestUVCSetup(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.http = mock.MagicMock() - self.hass.config.components = set(['http']) + mock_http_component(self.hass) def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 5fad8e16aed..846ecdc320f 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -1,10 +1,11 @@ """The tests for the generic_thermostat.""" +import asyncio import datetime import unittest from unittest import mock from homeassistant.core import callback -from homeassistant.bootstrap import setup_component +from homeassistant.bootstrap import setup_component, async_setup_component from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, SERVICE_TURN_OFF, @@ -105,23 +106,6 @@ class TestClimateGenericThermostat(unittest.TestCase): self.assertEqual(35, state.attributes.get('max_temp')) self.assertEqual(None, state.attributes.get('temperature')) - def test_custom_setup_params(self): - """Test the setup with custom parameters.""" - self.hass.config.components.remove(climate.DOMAIN) - assert setup_component(self.hass, climate.DOMAIN, {'climate': { - 'platform': 'generic_thermostat', - 'name': 'test', - 'heater': ENT_SWITCH, - 'target_sensor': ENT_SENSOR, - 'min_temp': MIN_TEMP, - 'max_temp': MAX_TEMP, - 'target_temp': TARGET_TEMP, - }}) - state = self.hass.states.get(ENTITY) - self.assertEqual(MIN_TEMP, state.attributes.get('min_temp')) - self.assertEqual(MAX_TEMP, state.attributes.get('max_temp')) - self.assertEqual(TARGET_TEMP, state.attributes.get('temperature')) - def test_set_target_temp(self): """Test the setting of the target temperature.""" climate.set_temperature(self.hass, 30) @@ -538,3 +522,23 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.services.register('switch', SERVICE_TURN_ON, log_call) self.hass.services.register('switch', SERVICE_TURN_OFF, log_call) + + +@asyncio.coroutine +def test_custom_setup_params(hass): + """Test the setup with custom parameters.""" + result = yield from async_setup_component( + hass, climate.DOMAIN, {'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR, + 'min_temp': MIN_TEMP, + 'max_temp': MAX_TEMP, + 'target_temp': TARGET_TEMP, + }}) + assert result + state = hass.states.get(ENTITY) + assert state.attributes.get('min_temp') == MIN_TEMP + assert state.attributes.get('max_temp') == MAX_TEMP + assert state.attributes.get('temperature') == TARGET_TEMP diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index fa5629e88c4..b9c2a1739c5 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -15,6 +15,9 @@ def test_validate_config_ok(hass, test_client): with patch.object(config, 'SECTIONS', ['core']): yield from async_setup_component(hass, 'config', {}) + # yield from hass.async_block_till_done() + yield from asyncio.sleep(0.1, loop=hass.loop) + hass.http.views[CheckConfigView.name].register(app.router) client = yield from test_client(app) diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 07baec9e3ae..1c37683969b 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -8,7 +8,7 @@ from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.bootstrap import async_setup_component, ATTR_COMPONENT from homeassistant.components import config -from tests.common import mock_http_component, mock_coro +from tests.common import mock_http_component, mock_coro, mock_component @pytest.fixture(autouse=True) @@ -27,7 +27,7 @@ def test_config_setup(hass, loop): @asyncio.coroutine def test_load_on_demand_already_loaded(hass, test_client): """Test getting suites.""" - hass.config.components.add('zwave') + mock_component(hass, 'zwave') with patch.object(config, 'SECTIONS', []), \ patch.object(config, 'ON_DEMAND', ['zwave']), \ diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index 81518458e0e..1d670d81b6e 100644 --- a/tests/components/cover/test_mqtt.py +++ b/tests/components/cover/test_mqtt.py @@ -4,9 +4,9 @@ import unittest from homeassistant.bootstrap import setup_component from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN import homeassistant.components.cover as cover -from tests.common import mock_mqtt_component, fire_mqtt_message -from tests.common import get_test_home_assistant +from tests.common import ( + get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) class TestCoverMQTT(unittest.TestCase): @@ -23,7 +23,6 @@ class TestCoverMQTT(unittest.TestCase): def test_state_via_state_topic(self): """Test the controlling state via topic.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -72,7 +71,6 @@ class TestCoverMQTT(unittest.TestCase): def test_state_via_template(self): """Test the controlling state via topic.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -101,7 +99,6 @@ class TestCoverMQTT(unittest.TestCase): def test_optimistic_state_change(self): """Test changing state optimistically.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -132,7 +129,6 @@ class TestCoverMQTT(unittest.TestCase): def test_send_open_cover_command(self): """Test the sending of open_cover.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -156,7 +152,6 @@ class TestCoverMQTT(unittest.TestCase): def test_send_close_cover_command(self): """Test the sending of close_cover.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -180,7 +175,6 @@ class TestCoverMQTT(unittest.TestCase): def test_send_stop__cover_command(self): """Test the sending of stop_cover.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', @@ -204,7 +198,6 @@ class TestCoverMQTT(unittest.TestCase): def test_current_cover_position(self): """Test the current cover position.""" - self.hass.config.components = set(['mqtt']) self.assertTrue(setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/cover/test_rfxtrx.py b/tests/components/cover/test_rfxtrx.py index 18e2051afd6..2d11e03cb41 100644 --- a/tests/components/cover/test_rfxtrx.py +++ b/tests/components/cover/test_rfxtrx.py @@ -6,7 +6,7 @@ import pytest from homeassistant.bootstrap import setup_component from homeassistant.components import rfxtrx as rfxtrx_core -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component @pytest.mark.skipif("os.environ.get('RFXTRX') != 'RUN'") @@ -16,7 +16,7 @@ class TestCoverRfxtrx(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['rfxtrx']) + mock_component('rfxtrx') def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index 48160cbb3d5..406087b7b99 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -17,7 +17,8 @@ from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) from tests.common import ( - get_test_home_assistant, get_test_config_dir, assert_setup_component) + get_test_home_assistant, get_test_config_dir, assert_setup_component, + mock_component) FAKEFILE = None @@ -43,7 +44,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): def setup_method(self, _): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['zone']) + mock_component(self.hass, 'zone') def teardown_method(self, _): """Stop everything that was started.""" diff --git a/tests/components/device_tracker/test_ddwrt.py b/tests/components/device_tracker/test_ddwrt.py index 340bac254b1..a0433b04d01 100644 --- a/tests/components/device_tracker/test_ddwrt.py +++ b/tests/components/device_tracker/test_ddwrt.py @@ -16,7 +16,8 @@ from homeassistant.components.device_tracker import DOMAIN from homeassistant.util import slugify from tests.common import ( - get_test_home_assistant, assert_setup_component, load_fixture) + get_test_home_assistant, assert_setup_component, load_fixture, + mock_component) from ...test_util.aiohttp import mock_aiohttp_client @@ -39,7 +40,7 @@ class TestDdwrt(unittest.TestCase): def setup_method(self, _): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['zone']) + mock_component(self.hass, 'zone') def teardown_method(self, _): """Stop everything that was started.""" diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py index 3ce3a358b87..583b9b86383 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -43,7 +43,6 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): dev_id = 'paulus' topic = '/location/paulus' - self.hass.config.components = set(['mqtt', 'zone']) assert setup_component(self.hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: 'mqtt', diff --git a/tests/components/device_tracker/test_upc_connect.py b/tests/components/device_tracker/test_upc_connect.py index 010c597cc31..7a1e14a7dfc 100644 --- a/tests/components/device_tracker/test_upc_connect.py +++ b/tests/components/device_tracker/test_upc_connect.py @@ -13,7 +13,8 @@ import homeassistant.components.device_tracker.upc_connect as platform from homeassistant.util.async import run_coroutine_threadsafe from tests.common import ( - get_test_home_assistant, assert_setup_component, load_fixture) + get_test_home_assistant, assert_setup_component, load_fixture, + mock_component) _LOGGER = logging.getLogger(__name__) @@ -30,7 +31,7 @@ class TestUPCConnect(object): def setup_method(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['zone']) + mock_component(self.hass, 'zone') self.host = "127.0.0.1" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 5fa37012c7a..36f434664d7 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -1,7 +1,6 @@ """The tests for the Home Assistant HTTP component.""" import asyncio import requests -from unittest.mock import MagicMock from homeassistant import bootstrap, const import homeassistant.components.http as http @@ -157,46 +156,48 @@ def test_registering_view_while_running(hass, test_client): assert text == 'hello' -def test_api_base_url(loop): +@asyncio.coroutine +def test_api_base_url_with_domain(hass): """Test setting api url.""" - hass = MagicMock() - hass.loop = loop - - assert loop.run_until_complete( - bootstrap.async_setup_component(hass, 'http', { - 'http': { - 'base_url': 'example.com' - } - }) - ) - + result = yield from bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'base_url': 'example.com' + } + }) + assert result assert hass.config.api.base_url == 'http://example.com' - assert loop.run_until_complete( - bootstrap.async_setup_component(hass, 'http', { - 'http': { - 'server_host': '1.1.1.1' - } - }) - ) +@asyncio.coroutine +def test_api_base_url_with_ip(hass): + """Test setting api url.""" + result = yield from bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'server_host': '1.1.1.1' + } + }) + assert result assert hass.config.api.base_url == 'http://1.1.1.1:8123' - assert loop.run_until_complete( - bootstrap.async_setup_component(hass, 'http', { - 'http': { - 'server_host': '1.1.1.1' - } - }) - ) - assert hass.config.api.base_url == 'http://1.1.1.1:8123' +@asyncio.coroutine +def test_api_base_url_with_ip_port(hass): + """Test setting api url.""" + result = yield from bootstrap.async_setup_component(hass, 'http', { + 'http': { + 'base_url': '1.1.1.1:8124' + } + }) + assert result + assert hass.config.api.base_url == 'http://1.1.1.1:8124' - assert loop.run_until_complete( - bootstrap.async_setup_component(hass, 'http', { - 'http': { - } - }) - ) +@asyncio.coroutine +def test_api_no_base_url(hass): + """Test setting api url.""" + result = yield from bootstrap.async_setup_component(hass, 'http', { + 'http': { + } + }) + assert result assert hass.config.api.base_url == 'http://127.0.0.1:8123' diff --git a/tests/components/light/test_demo.py b/tests/components/light/test_demo.py index f8b46579187..391a6d05903 100644 --- a/tests/components/light/test_demo.py +++ b/tests/components/light/test_demo.py @@ -8,7 +8,7 @@ from homeassistant.bootstrap import setup_component, async_setup_component import homeassistant.components.light as light from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component ENTITY_LIGHT = 'light.bed_light' @@ -68,7 +68,7 @@ class TestDemoLight(unittest.TestCase): @asyncio.coroutine def test_restore_state(hass): """Test state gets restored.""" - hass.config.components.add('recorder') + mock_component(hass, 'recorder') hass.state = CoreState.starting hass.data[DATA_RESTORE_CACHE] = { 'light.bed_light': State('light.bed_light', 'on', { diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 4f0d4a273b6..410f947178c 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -100,7 +100,6 @@ class TestLightMQTT(unittest.TestCase): def test_fail_setup_if_no_command_topic(self): """Test if command fails with command topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(0): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -113,7 +112,6 @@ class TestLightMQTT(unittest.TestCase): def test_no_color_or_brightness_or_color_temp_if_no_topics(self): \ # pylint: disable=invalid-name """Test if there is no color and brightness if no topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -158,7 +156,6 @@ class TestLightMQTT(unittest.TestCase): 'payload_off': 0 }} - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, config) @@ -214,7 +211,6 @@ class TestLightMQTT(unittest.TestCase): def test_controlling_scale(self): """Test the controlling scale.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -277,7 +273,6 @@ class TestLightMQTT(unittest.TestCase): 'rgb_value_template': '{{ value_json.hello | join(",") }}', }} - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, config) @@ -317,7 +312,6 @@ class TestLightMQTT(unittest.TestCase): 'payload_off': 'off' }} - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, config) @@ -367,7 +361,6 @@ class TestLightMQTT(unittest.TestCase): 'state_topic': 'test_light_rgb/status', }} - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, config) @@ -392,7 +385,6 @@ class TestLightMQTT(unittest.TestCase): 'state_topic': 'test_light_rgb/status' }} - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, config) diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 4f48181a917..55c437cdc79 100755 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -53,7 +53,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_fail_setup_if_no_command_topic(self): \ # pylint: disable=invalid-name """Test if setup fails with no command topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(0): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -66,7 +65,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_no_color_or_brightness_if_no_config(self): \ # pylint: disable=invalid-name """Test if there is no color and brightness if they aren't defined.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', @@ -92,7 +90,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_controlling_state_via_topic(self): \ # pylint: disable=invalid-name """Test the controlling of the state via topic.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', @@ -152,7 +149,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_sending_mqtt_commands_and_optimistic(self): \ # pylint: disable=invalid-name """Test the sending of command in optimistic mode.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', @@ -208,7 +204,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_flash_short_and_long(self): \ # pylint: disable=invalid-name """Test for flash length being sent when included.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', @@ -250,7 +245,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_transition(self): """Test for transition time being sent when included.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', @@ -292,7 +286,6 @@ class TestLightMQTTJSON(unittest.TestCase): def test_invalid_color_and_brightness_values(self): \ # pylint: disable=invalid-name """Test that invalid color/brightness values are ignored.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index e097aba92a9..020ded1bd80 100755 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -45,7 +45,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_setup_fails(self): \ # pylint: disable=invalid-name """Test that setup fails with missing required configuration items.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(0): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -58,7 +57,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_state_change_via_topic(self): \ # pylint: disable=invalid-name """Test state change via topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -93,7 +91,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_state_brightness_color_effect_change_via_topic(self): \ # pylint: disable=invalid-name """Test state, brightness, color and effect change via topic.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -170,7 +167,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_optimistic(self): \ # pylint: disable=invalid-name """Test optimistic mode.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -232,7 +228,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_flash(self): \ # pylint: disable=invalid-name """Test flash.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -276,7 +271,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_transition(self): """Test for transition time being sent when included.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { @@ -320,7 +314,6 @@ class TestLightMQTTTemplate(unittest.TestCase): def test_invalid_values(self): \ # pylint: disable=invalid-name """Test that invalid values are ignored.""" - self.hass.config.components = set(['mqtt']) with assert_setup_component(1): assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { diff --git a/tests/components/light/test_rfxtrx.py b/tests/components/light/test_rfxtrx.py index ca50a9cc925..135e51380cd 100644 --- a/tests/components/light/test_rfxtrx.py +++ b/tests/components/light/test_rfxtrx.py @@ -6,7 +6,7 @@ import pytest from homeassistant.bootstrap import setup_component from homeassistant.components import rfxtrx as rfxtrx_core -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component @pytest.mark.skipif("os.environ.get('RFXTRX') != 'RUN'") @@ -16,7 +16,7 @@ class TestLightRfxtrx(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['rfxtrx']) + mock_component(self.hass, 'rfxtrx') def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index c858d58dfa7..14714e9a3d1 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -23,7 +23,6 @@ class TestLockMQTT(unittest.TestCase): def test_controlling_state_via_topic(self): """Test the controlling state via topic.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, lock.DOMAIN, { lock.DOMAIN: { 'platform': 'mqtt', @@ -53,7 +52,6 @@ class TestLockMQTT(unittest.TestCase): def test_sending_mqtt_commands_and_optimistic(self): """Test the sending MQTT commands in optimistic mode.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, lock.DOMAIN, { lock.DOMAIN: { 'platform': 'mqtt', @@ -87,7 +85,6 @@ class TestLockMQTT(unittest.TestCase): def test_controlling_state_via_topic_and_json_message(self): """Test the controlling state via topic and JSON message.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, lock.DOMAIN, { lock.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 1ca0846b1fd..3ccfcd7eb64 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -1,5 +1,4 @@ """The tests for the Universal Media player platform.""" -import asyncio from copy import copy import unittest @@ -258,7 +257,6 @@ class TestMediaPlayer(unittest.TestCase): bad_config = {'platform': 'universal'} entities = [] - @asyncio.coroutine def add_devices(new_entities): """Add devices to list.""" for dev in new_entities: diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py index cfef8ebcc16..db9e963d84c 100644 --- a/tests/components/mqtt/test_server.py +++ b/tests/components/mqtt/test_server.py @@ -4,7 +4,8 @@ from unittest.mock import Mock, MagicMock, patch from homeassistant.bootstrap import setup_component import homeassistant.components.mqtt as mqtt -from tests.common import get_test_home_assistant, mock_coro +from tests.common import ( + get_test_home_assistant, mock_coro, mock_http_component) class TestMQTT: @@ -13,7 +14,7 @@ class TestMQTT: def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('http') + mock_http_component(self.hass, 'super_secret') def teardown_method(self, method): """Stop everything that was started.""" @@ -33,13 +34,21 @@ class TestMQTT: self.hass.config.api = MagicMock(api_password=password) assert setup_component(self.hass, mqtt.DOMAIN, {}) assert mock_mqtt.called + from pprint import pprint + pprint(mock_mqtt.mock_calls) assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant' assert mock_mqtt.mock_calls[1][1][6] == password - mock_mqtt.reset_mock() + @patch('passlib.apps.custom_app_context', Mock(return_value='')) + @patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock())) + @patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock())) + @patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro())) + @patch('homeassistant.components.mqtt.MQTT') + def test_creating_config_with_http_no_pass(self, mock_mqtt): + """Test if the MQTT server gets started and subscribe/publish msg.""" mock_mqtt().async_connect.return_value = mock_coro(True) + self.hass.bus.listen_once = MagicMock() - self.hass.config.components = set(['http']) self.hass.config.api = MagicMock(api_password=None) assert setup_component(self.hass, mqtt.DOMAIN, {}) assert mock_mqtt.called diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index de13f678ae0..43c5e78c5da 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -1,5 +1,4 @@ """The tests for the notify demo platform.""" -import asyncio import unittest from unittest.mock import patch @@ -17,12 +16,6 @@ CONFIG = { } -@asyncio.coroutine -def mock_setup_platform(): - """Mock prepare_setup_platform.""" - return None - - class TestNotifyDemo(unittest.TestCase): """Test the demo notify.""" @@ -52,15 +45,6 @@ class TestNotifyDemo(unittest.TestCase): """Test setup.""" self._setup_notify() - @patch('homeassistant.bootstrap.async_prepare_setup_platform', - return_value=mock_setup_platform()) - def test_no_prepare_setup_platform(self, mock_prep_setup_platform): - """Test missing notify platform.""" - with assert_setup_component(0): - setup_component(self.hass, notify.DOMAIN, CONFIG) - - assert mock_prep_setup_platform.called - @patch('homeassistant.components.notify.demo.get_service', autospec=True) def test_no_notify_service(self, mock_demo_get_service): """Test missing platform notify service instance.""" diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 771aa999210..1de9d2f731a 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -5,7 +5,7 @@ from homeassistant.bootstrap import setup_component import homeassistant.components.sensor as sensor from tests.common import mock_mqtt_component, fire_mqtt_message -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component class TestSensorMQTT(unittest.TestCase): @@ -22,7 +22,7 @@ class TestSensorMQTT(unittest.TestCase): def test_setting_sensor_value_via_mqtt_message(self): """Test the setting of the value via MQTT.""" - self.hass.config.components = set(['mqtt']) + mock_component(self.hass, 'mqtt') assert setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { 'platform': 'mqtt', @@ -42,7 +42,7 @@ class TestSensorMQTT(unittest.TestCase): def test_setting_sensor_value_via_mqtt_json_message(self): """Test the setting of the value via MQTT with JSON playload.""" - self.hass.config.components = set(['mqtt']) + mock_component(self.hass, 'mqtt') assert setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/sensor/test_pilight.py b/tests/components/sensor/test_pilight.py index 2bade2af1a3..35b6924a35a 100644 --- a/tests/components/sensor/test_pilight.py +++ b/tests/components/sensor/test_pilight.py @@ -5,7 +5,8 @@ from homeassistant.bootstrap import setup_component import homeassistant.components.sensor as sensor from homeassistant.components import pilight -from tests.common import get_test_home_assistant, assert_setup_component +from tests.common import ( + get_test_home_assistant, assert_setup_component, mock_component) HASS = None @@ -23,7 +24,7 @@ def setup_function(): global HASS HASS = get_test_home_assistant() - HASS.config.components = set(['pilight']) + mock_component(HASS, 'pilight') # pylint: disable=invalid-name diff --git a/tests/components/sensor/test_rfxtrx.py b/tests/components/sensor/test_rfxtrx.py index 092a9b60f85..96b5623b7b1 100644 --- a/tests/components/sensor/test_rfxtrx.py +++ b/tests/components/sensor/test_rfxtrx.py @@ -7,7 +7,7 @@ from homeassistant.bootstrap import setup_component from homeassistant.components import rfxtrx as rfxtrx_core from homeassistant.const import TEMP_CELSIUS -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component @pytest.mark.skipif("os.environ.get('RFXTRX') != 'RUN'") @@ -17,7 +17,7 @@ class TestSensorRfxtrx(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['rfxtrx']) + mock_component(self.hass, 'rfxtrx') def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 3a5502c8150..33de6de52a9 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -22,7 +22,6 @@ class TestSensorMQTT(unittest.TestCase): def test_controlling_state_via_topic(self): """Test the controlling state via topic.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, switch.DOMAIN, { switch.DOMAIN: { 'platform': 'mqtt', @@ -52,7 +51,6 @@ class TestSensorMQTT(unittest.TestCase): def test_sending_mqtt_commands_and_optimistic(self): """Test the sending MQTT commands in optimistic mode.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, switch.DOMAIN, { switch.DOMAIN: { 'platform': 'mqtt', @@ -86,7 +84,6 @@ class TestSensorMQTT(unittest.TestCase): def test_controlling_state_via_topic_and_json_message(self): """Test the controlling state via topic and JSON message.""" - self.hass.config.components = set(['mqtt']) assert setup_component(self.hass, switch.DOMAIN, { switch.DOMAIN: { 'platform': 'mqtt', diff --git a/tests/components/switch/test_rfxtrx.py b/tests/components/switch/test_rfxtrx.py index 26af42be4a9..b4eb1259515 100644 --- a/tests/components/switch/test_rfxtrx.py +++ b/tests/components/switch/test_rfxtrx.py @@ -6,7 +6,7 @@ import pytest from homeassistant.bootstrap import setup_component from homeassistant.components import rfxtrx as rfxtrx_core -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component @pytest.mark.skipif("os.environ.get('RFXTRX') != 'RUN'") @@ -16,7 +16,7 @@ class TestSwitchRfxtrx(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components = set(['rfxtrx']) + mock_component(self.hass, 'rfxtrx') def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py index c22c431ed03..62b9f681703 100644 --- a/tests/components/test_input_boolean.py +++ b/tests/components/test_input_boolean.py @@ -4,7 +4,7 @@ import asyncio import unittest import logging -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component from homeassistant.core import CoreState, State from homeassistant.bootstrap import setup_component, async_setup_component @@ -118,7 +118,7 @@ def test_restore_state(hass): } hass.state = CoreState.starting - hass.config.components.add('recorder') + mock_component(hass, 'recorder') yield from async_setup_component(hass, DOMAIN, { DOMAIN: { diff --git a/tests/components/test_panel_custom.py b/tests/components/test_panel_custom.py index b07c62e441f..46de75bf8bd 100644 --- a/tests/components/test_panel_custom.py +++ b/tests/components/test_panel_custom.py @@ -39,6 +39,7 @@ class TestPanelCustom(unittest.TestCase): path = self.hass.config.path(panel_custom.PANEL_DIR) os.mkdir(path) + self.hass.data.pop(bootstrap.DATA_SETUP) with open(os.path.join(path, 'todomvc.html'), 'a'): assert bootstrap.setup_component(self.hass, 'panel_custom', config) @@ -66,6 +67,8 @@ class TestPanelCustom(unittest.TestCase): ) assert not mock_register.called + self.hass.data.pop(bootstrap.DATA_SETUP) + with patch('os.path.isfile', Mock(return_value=True)): with patch('os.access', Mock(return_value=True)): assert bootstrap.setup_component( diff --git a/tests/components/test_rfxtrx.py b/tests/components/test_rfxtrx.py index 7e47dfb6a50..a1041777ebc 100644 --- a/tests/components/test_rfxtrx.py +++ b/tests/components/test_rfxtrx.py @@ -50,8 +50,8 @@ class TestRFXTRX(unittest.TestCase): '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', 'dummy': True}})) - self.hass.config.components.remove('rfxtrx') - + def test_valid_config2(self): + """Test configuration.""" self.assertTrue(setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + diff --git a/tests/components/test_script.py b/tests/components/test_script.py index 4e8d94ade21..14aa75eb963 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -6,7 +6,7 @@ from homeassistant.core import callback from homeassistant.bootstrap import setup_component from homeassistant.components import script -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_component ENTITY_ID = 'script.test' @@ -19,7 +19,7 @@ class TestScriptComponent(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.components.add('group') + mock_component(self.hass, 'group') # pylint: disable=invalid-name def tearDown(self): diff --git a/tests/components/test_zone.py b/tests/components/test_zone.py index 4eefe8c0031..b0d4f06688d 100644 --- a/tests/components/test_zone.py +++ b/tests/components/test_zone.py @@ -63,11 +63,12 @@ class TestComponentZone(unittest.TestCase): }, ] }) - + self.hass.block_till_done() active = zone.active_zone(self.hass, 32.880600, -117.237561) assert active is None - self.hass.config.components.remove('zone') + def test_active_zone_skips_passive_zones_2(self): + """Test active and passive zones.""" assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { @@ -78,7 +79,7 @@ class TestComponentZone(unittest.TestCase): }, ] }) - + self.hass.block_till_done() active = zone.active_zone(self.hass, 32.880700, -117.237561) assert 'zone.active_zone' == active.entity_id @@ -106,7 +107,10 @@ class TestComponentZone(unittest.TestCase): active = zone.active_zone(self.hass, latitude, longitude) assert 'zone.small_zone' == active.entity_id - self.hass.config.components.remove('zone') + def test_active_zone_prefers_smaller_zone_if_same_distance_2(self): + """Test zone size preferences.""" + latitude = 32.880600 + longitude = -117.237561 assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index b2f60cd0a2e..5e3f9cd8c88 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -1,6 +1,5 @@ """Test discovery helpers.""" import asyncio -from collections import OrderedDict from unittest.mock import patch import pytest @@ -9,7 +8,6 @@ from homeassistant import loader, bootstrap from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery -from homeassistant.util.async import run_coroutine_threadsafe from tests.common import ( get_test_home_assistant, MockModule, MockPlatform, mock_coro) @@ -145,10 +143,6 @@ class TestHelpersDiscovery: }], }) - # We wait for the setup_lock to finish - run_coroutine_threadsafe( - self.hass.data['setup_lock'].acquire(), self.hass.loop).result() - self.hass.block_till_done() # test_component will only be setup once @@ -171,6 +165,7 @@ class TestHelpersDiscovery: def component1_setup(hass, config): """Setup mock component.""" + print('component1 setup') discovery.discover(hass, 'test_component2', component='test_component2') return True @@ -188,15 +183,15 @@ class TestHelpersDiscovery: 'test_component2', MockModule('test_component2', setup=component2_setup)) - config = OrderedDict() - config['test_component1'] = {} - config['test_component2'] = {} - - self.hass.loop.run_until_complete = \ - lambda _: self.hass.block_till_done() - - bootstrap.from_config_dict(config, self.hass) + @callback + def setup(): + """Setup 2 components.""" + self.hass.async_add_job(bootstrap.async_setup_component( + self.hass, 'test_component1', {})) + self.hass.async_add_job(bootstrap.async_setup_component( + self.hass, 'test_component2', {})) + self.hass.add_job(setup) self.hass.block_till_done() # test_component will only be setup once diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index d5ae60cc18e..d95ec3a87f8 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -272,6 +272,7 @@ class TestHelpersEntityComponent(unittest.TestCase): } }) + self.hass.block_till_done() assert component_setup.called assert platform_setup.called @@ -294,6 +295,7 @@ class TestHelpersEntityComponent(unittest.TestCase): ("{} 3".format(DOMAIN), {'platform': 'mod2'}), ])) + self.hass.block_till_done() assert platform1_setup.called assert platform2_setup.called @@ -336,6 +338,7 @@ class TestHelpersEntityComponent(unittest.TestCase): } }) + self.hass.block_till_done() assert mock_track.called assert timedelta(seconds=30) == mock_track.call_args[0][2] @@ -360,6 +363,7 @@ class TestHelpersEntityComponent(unittest.TestCase): } }) + self.hass.block_till_done() assert mock_track.called assert timedelta(seconds=30) == mock_track.call_args[0][2] @@ -385,6 +389,8 @@ class TestHelpersEntityComponent(unittest.TestCase): } }) + self.hass.block_till_done() + assert sorted(self.hass.states.entity_ids()) == \ ['test_domain.yummy_beer', 'test_domain.yummy_unnamed_device'] diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index 59598823911..f46f33c333f 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -13,13 +13,14 @@ from homeassistant.helpers.restore_state import ( from homeassistant.components.recorder.models import RecorderRuns, States from tests.common import ( - get_test_home_assistant, mock_coro, init_recorder_component) + get_test_home_assistant, mock_coro, init_recorder_component, + mock_component) @asyncio.coroutine def test_caching_data(hass): """Test that we cache data.""" - hass.config.components.add('recorder') + mock_component(hass, 'recorder') hass.state = CoreState.starting states = [ diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 410f1636a88..173cea1957a 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -7,12 +7,10 @@ import threading import logging import voluptuous as vol -import pytest from homeassistant.core import callback from homeassistant.const import EVENT_HOMEASSISTANT_START import homeassistant.config as config_util -from homeassistant.exceptions import HomeAssistantError from homeassistant import bootstrap, loader import homeassistant.util.dt as dt_util from homeassistant.helpers.config_validation import PLATFORM_SCHEMA @@ -79,23 +77,8 @@ class TestBootstrap: patch_yaml_files(files, True): self.hass = bootstrap.from_config_file('config.yaml') - components.add('group') assert components == self.hass.config.components - def test_handle_setup_circular_dependency(self): - """Test the setup of circular dependencies.""" - loader.set_component('comp_b', MockModule('comp_b', ['comp_a'])) - - def setup_a(hass, config): - """Setup the another component.""" - bootstrap.setup_component(hass, 'comp_b') - return True - - loader.set_component('comp_a', MockModule('comp_a', setup=setup_a)) - - bootstrap.setup_component(self.hass, 'comp_a') - assert set(['comp_a']) == self.hass.config.components - def test_validate_component_config(self): """Test validating component configuration.""" config_schema = vol.Schema({ @@ -109,16 +92,22 @@ class TestBootstrap: with assert_setup_component(0): assert not bootstrap.setup_component(self.hass, 'comp_conf', {}) + self.hass.data.pop(bootstrap.DATA_SETUP) + with assert_setup_component(0): assert not bootstrap.setup_component(self.hass, 'comp_conf', { 'comp_conf': None }) + self.hass.data.pop(bootstrap.DATA_SETUP) + with assert_setup_component(0): assert not bootstrap.setup_component(self.hass, 'comp_conf', { 'comp_conf': {} }) + self.hass.data.pop(bootstrap.DATA_SETUP) + with assert_setup_component(0): assert not bootstrap.setup_component(self.hass, 'comp_conf', { 'comp_conf': { @@ -127,6 +116,8 @@ class TestBootstrap: } }) + self.hass.data.pop(bootstrap.DATA_SETUP) + with assert_setup_component(1): assert bootstrap.setup_component(self.hass, 'comp_conf', { 'comp_conf': { @@ -154,6 +145,7 @@ class TestBootstrap: } }) + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): @@ -167,6 +159,7 @@ class TestBootstrap: } }) + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(0): @@ -177,6 +170,7 @@ class TestBootstrap: } }) + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): @@ -187,6 +181,7 @@ class TestBootstrap: } }) + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): @@ -197,6 +192,7 @@ class TestBootstrap: }] }) + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('platform_conf') # Any falsey platform config will be ignored (None, {}, etc) @@ -244,22 +240,27 @@ class TestBootstrap: def test_component_not_setup_twice_if_loaded_during_other_setup(self): """Test component setup while waiting for lock is not setup twice.""" - loader.set_component('comp', MockModule('comp')) - result = [] + @asyncio.coroutine + def async_setup(hass, config): + """Tracking Setup.""" + result.append(1) + + loader.set_component( + 'comp', MockModule('comp', async_setup=async_setup)) + def setup_component(): """Setup the component.""" - result.append(bootstrap.setup_component(self.hass, 'comp')) + bootstrap.setup_component(self.hass, 'comp') thread = threading.Thread(target=setup_component) thread.start() - self.hass.config.components.add('comp') + bootstrap.setup_component(self.hass, 'comp') thread.join() assert len(result) == 1 - assert result[0] def test_component_not_setup_missing_dependencies(self): """Test we do not setup a component if not all dependencies loaded.""" @@ -269,8 +270,9 @@ class TestBootstrap: assert not bootstrap.setup_component(self.hass, 'comp', {}) assert 'comp' not in self.hass.config.components - loader.set_component('non_existing', MockModule('non_existing')) + self.hass.data.pop(bootstrap.DATA_SETUP) + loader.set_component('non_existing', MockModule('non_existing')) assert bootstrap.setup_component(self.hass, 'comp', {}) def test_component_failing_setup(self): @@ -349,6 +351,7 @@ class TestBootstrap: }) assert mock_setup.call_count == 0 + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('switch') with assert_setup_component(0): @@ -361,6 +364,7 @@ class TestBootstrap: }) assert mock_setup.call_count == 0 + self.hass.data.pop(bootstrap.DATA_SETUP) self.hass.config.components.remove('switch') with assert_setup_component(1): @@ -382,6 +386,7 @@ class TestBootstrap: assert loader.get_component('disabled_component') is None assert 'disabled_component' not in self.hass.config.components + self.hass.data.pop(bootstrap.DATA_SETUP) loader.set_component( 'disabled_component', MockModule('disabled_component', setup=lambda hass, config: False)) @@ -390,6 +395,7 @@ class TestBootstrap: assert loader.get_component('disabled_component') is not None assert 'disabled_component' not in self.hass.config.components + self.hass.data.pop(bootstrap.DATA_SETUP) loader.set_component( 'disabled_component', MockModule('disabled_component', setup=lambda hass, config: True)) @@ -435,35 +441,16 @@ class TestBootstrap: self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START, track_start) - self.hass.loop.run_until_complete = \ - lambda _: self.hass.block_till_done() - - bootstrap.from_config_dict({'test_component1': None}, self.hass) - + self.hass.add_job(bootstrap.async_setup_component( + self.hass, 'test_component1', {})) + self.hass.block_till_done() self.hass.start() - assert call_order == [1, 1, 2] @asyncio.coroutine def test_component_cannot_depend_config(hass): """Test config is not allowed to be a dependency.""" - loader.set_component( - 'test_component1', - MockModule('test_component1', dependencies=['config'])) - - with pytest.raises(HomeAssistantError): - yield from bootstrap.async_from_config_dict( - {'test_component1': None}, hass) - - -@asyncio.coroutine -def test_platform_cannot_depend_config(): - """Test config is not allowed to be a dependency.""" - loader.set_component( - 'test_component1.test', - MockPlatform('whatever', dependencies=['config'])) - - with pytest.raises(HomeAssistantError): - yield from bootstrap.async_prepare_setup_platform( - mock.MagicMock(), {}, 'test_component1', 'test') + result = yield from bootstrap._async_process_dependencies( + hass, None, 'test', ['config']) + assert not result diff --git a/tests/test_loader.py b/tests/test_loader.py index 93e24b57205..0b3f9653faa 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -54,33 +54,3 @@ class TestLoader(unittest.TestCase): # Try to get load order for non-existing component self.assertEqual([], loader.load_order_component('mod1')) - - def test_load_order_components(self): - """Setup loading order of components.""" - loader.set_component('mod1', MockModule('mod1', ['group'])) - loader.set_component('mod2', MockModule('mod2', ['mod1', 'sun'])) - loader.set_component('mod3', MockModule('mod3', ['mod2'])) - loader.set_component('mod4', MockModule('mod4', ['group'])) - - self.assertEqual( - ['group', 'mod4', 'mod1', 'sun', 'mod2', 'mod3'], - loader.load_order_components(['mod4', 'mod3', 'mod2'])) - - loader.set_component('mod1', MockModule('mod1')) - loader.set_component('mod2', MockModule('mod2', ['group'])) - - self.assertEqual( - ['mod1', 'group', 'mod2'], - loader.load_order_components(['mod2', 'mod1'])) - - # Add a non existing one - self.assertEqual( - ['mod1', 'group', 'mod2'], - loader.load_order_components(['mod2', 'nonexisting', 'mod1'])) - - # Depend on a non existing one - loader.set_component('mod1', MockModule('mod1', ['nonexisting'])) - - self.assertEqual( - ['group', 'mod2'], - loader.load_order_components(['mod2', 'mod1']))