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'
@ -79,10 +73,21 @@ def _handle_requirements(hass, component, name):
def _setup_component(hass, domain, config):
"""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)
with _SETUP_LOCK:
# It might have been loaded while waiting for lock
if domain in hass.config.components:
return True
if domain in _CURRENT_SETUP:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
return False
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
@ -95,6 +100,8 @@ def _setup_component(hass, domain, config):
if not _handle_requirements(hass, component, domain):
return False
_CURRENT_SETUP.append(domain)
try:
if not component.setup(hass, config):
_LOGGER.error('component %s failed to initialize', domain)
@ -102,6 +109,8 @@ def _setup_component(hass, domain, config):
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)

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)