mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Handle circular setup dependency
This commit is contained in:
parent
4c538c718b
commit
1bfea626ff
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user