Handle circular setup dependency
parent
4c538c718b
commit
1bfea626ff
|
@ -1,13 +1,4 @@
|
||||||
"""
|
"""Provides methods to bootstrap a home assistant instance."""
|
||||||
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)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
@ -15,6 +6,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
import homeassistant.components.group as group
|
import homeassistant.components.group as group
|
||||||
|
@ -32,6 +24,8 @@ from homeassistant.helpers import event_decorators, service
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_SETUP_LOCK = RLock()
|
||||||
|
_CURRENT_SETUP = []
|
||||||
|
|
||||||
ATTR_COMPONENT = 'component'
|
ATTR_COMPONENT = 'component'
|
||||||
|
|
||||||
|
@ -78,42 +72,57 @@ def _handle_requirements(hass, component, name):
|
||||||
|
|
||||||
|
|
||||||
def _setup_component(hass, domain, config):
|
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:
|
if domain in hass.config.components:
|
||||||
return True
|
return True
|
||||||
component = loader.get_component(domain)
|
|
||||||
|
|
||||||
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
|
with _SETUP_LOCK:
|
||||||
if dep not in hass.config.components]
|
# It might have been loaded while waiting for lock
|
||||||
|
if domain in hass.config.components:
|
||||||
|
return True
|
||||||
|
|
||||||
if missing_deps:
|
if domain in _CURRENT_SETUP:
|
||||||
_LOGGER.error(
|
_LOGGER.error('Attempt made to setup %s during setup of %s',
|
||||||
'Not initializing %s because not all dependencies loaded: %s',
|
domain, domain)
|
||||||
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)
|
|
||||||
return False
|
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
|
if missing_deps:
|
||||||
# it communicates with devices
|
_LOGGER.error(
|
||||||
if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
|
'Not initializing %s because not all dependencies loaded: %s',
|
||||||
hass.pool.add_worker()
|
domain, ", ".join(missing_deps))
|
||||||
|
return False
|
||||||
|
|
||||||
hass.bus.fire(
|
if not _handle_requirements(hass, component, domain):
|
||||||
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: 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):
|
def prepare_setup_platform(hass, config, domain, platform_name):
|
||||||
|
|
|
@ -146,7 +146,7 @@ class MockModule(object):
|
||||||
self.DEPENDENCIES = dependencies
|
self.DEPENDENCIES = dependencies
|
||||||
# Setup a mock setup if none given.
|
# Setup a mock setup if none given.
|
||||||
if setup is None:
|
if setup is None:
|
||||||
self.setup = lambda hass, config: False
|
self.setup = lambda hass, config: True
|
||||||
else:
|
else:
|
||||||
self.setup = setup
|
self.setup = setup
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@ import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap, loader
|
||||||
from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE,
|
from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE,
|
||||||
CONF_NAME, CONF_CUSTOMIZE)
|
CONF_NAME, CONF_CUSTOMIZE)
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.entity import Entity
|
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):
|
class TestBootstrap(unittest.TestCase):
|
||||||
|
@ -102,3 +102,17 @@ class TestBootstrap(unittest.TestCase):
|
||||||
state = hass.states.get('test.test')
|
state = hass.states.get('test.test')
|
||||||
|
|
||||||
self.assertTrue(state.attributes['hidden'])
|
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)
|
||||||
|
|
Loading…
Reference in New Issue