Handle circular setup dependency

This commit is contained in:
Paulus Schoutsen 2016-02-19 23:20:14 -08:00
parent 4c538c718b
commit 1bfea626ff
3 changed files with 63 additions and 40 deletions

View File

@ -1,13 +1,4 @@
"""
homeassistant.bootstrap
~~~~~~~~~~~~~~~~~~~~~~~
Provides methods to bootstrap a home assistant instance.
Each method will return a tuple (bus, statemachine).
After bootstrapping you can add your own components or
start by calling homeassistant.start_home_assistant(bus)
"""
"""Provides methods to bootstrap a home assistant instance."""
import logging
import logging.handlers
@ -15,6 +6,7 @@ import os
import shutil
import sys
from collections import defaultdict
from threading import RLock
import homeassistant.components as core_components
import homeassistant.components.group as group
@ -32,6 +24,8 @@ from homeassistant.helpers import event_decorators, service
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
_CURRENT_SETUP = []
ATTR_COMPONENT = 'component'
@ -78,42 +72,57 @@ def _handle_requirements(hass, component, name):
def _setup_component(hass, domain, config):
""" Setup a component for Home Assistant. """
"""Setup a component for Home Assistant."""
# pylint: disable=too-many-return-statements
if domain in hass.config.components:
return True
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
with _SETUP_LOCK:
# It might have been loaded while waiting for lock
if domain in hass.config.components:
return True
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False
if not _handle_requirements(hass, component, domain):
return False
try:
if not component.setup(hass, config):
_LOGGER.error('component %s failed to initialize', domain)
if domain in _CURRENT_SETUP:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
return False
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
return False
hass.config.components.append(component.DOMAIN)
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker()
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False
hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
if not _handle_requirements(hass, component, domain):
return False
return True
_CURRENT_SETUP.append(domain)
try:
if not component.setup(hass, config):
_LOGGER.error('component %s failed to initialize', domain)
return False
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
return False
finally:
_CURRENT_SETUP.remove(domain)
hass.config.components.append(component.DOMAIN)
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker()
hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
return True
def prepare_setup_platform(hass, config, domain, platform_name):

View File

@ -146,7 +146,7 @@ class MockModule(object):
self.DEPENDENCIES = dependencies
# Setup a mock setup if none given.
if setup is None:
self.setup = lambda hass, config: False
self.setup = lambda hass, config: True
else:
self.setup = setup

View File

@ -9,13 +9,13 @@ import os
import tempfile
import unittest
from homeassistant import bootstrap
from homeassistant import bootstrap, loader
from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE,
CONF_NAME, CONF_CUSTOMIZE)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from tests.common import get_test_home_assistant
from tests.common import get_test_home_assistant, MockModule
class TestBootstrap(unittest.TestCase):
@ -102,3 +102,17 @@ class TestBootstrap(unittest.TestCase):
state = hass.states.get('test.test')
self.assertTrue(state.attributes['hidden'])
def test_handle_setup_circular_dependency(self):
hass = get_test_home_assistant()
loader.set_component('comp_b', MockModule('comp_b', ['comp_a']))
def setup_a(hass, config):
bootstrap.setup_component(hass, 'comp_b')
return True
loader.set_component('comp_a', MockModule('comp_a', setup=setup_a))
bootstrap.setup_component(hass, 'comp_a')
self.assertEqual(['comp_a'], hass.config.components)