mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Fix bootstrap circular imports (#4108)
* Fix bootstrap circular imports * fix test * Lint
This commit is contained in:
parent
08a65a3b31
commit
9ea1101aba
@ -102,9 +102,18 @@ def _async_setup_component(hass: core.HomeAssistant,
|
|||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-return-statements,too-many-branches
|
# pylint: disable=too-many-return-statements,too-many-branches
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
|
if not hasattr(hass, 'setup_lock'):
|
||||||
|
hass.setup_lock = asyncio.Lock(loop=hass.loop)
|
||||||
|
|
||||||
if domain in hass.config.components:
|
if domain in hass.config.components:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
did_lock = False
|
||||||
|
if not hass.setup_lock.locked():
|
||||||
|
yield from hass.setup_lock.acquire()
|
||||||
|
did_lock = True
|
||||||
|
|
||||||
|
try:
|
||||||
if domain in _CURRENT_SETUP:
|
if domain in _CURRENT_SETUP:
|
||||||
_LOGGER.error('Attempt made to setup %s during setup of %s',
|
_LOGGER.error('Attempt made to setup %s during setup of %s',
|
||||||
domain, domain)
|
domain, domain)
|
||||||
@ -152,6 +161,9 @@ def _async_setup_component(hass: core.HomeAssistant,
|
|||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
finally:
|
||||||
|
if did_lock:
|
||||||
|
hass.setup_lock.release()
|
||||||
|
|
||||||
|
|
||||||
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"""Helper methods to help with platform discovery."""
|
"""Helper methods to help with platform discovery."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from homeassistant import bootstrap, core
|
from homeassistant import bootstrap, core
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED)
|
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED)
|
||||||
from homeassistant.util.async import run_callback_threadsafe
|
from homeassistant.util.async import (
|
||||||
|
run_callback_threadsafe, fire_coroutine_threadsafe)
|
||||||
|
|
||||||
EVENT_LOAD_PLATFORM = 'load_platform.{}'
|
EVENT_LOAD_PLATFORM = 'load_platform.{}'
|
||||||
ATTR_PLATFORM = 'platform'
|
ATTR_PLATFORM = 'platform'
|
||||||
@ -87,10 +89,43 @@ def load_platform(hass, component, platform, discovered=None,
|
|||||||
|
|
||||||
Use `listen_platform` to register a callback for these events.
|
Use `listen_platform` to register a callback for these events.
|
||||||
"""
|
"""
|
||||||
def discover_platform():
|
fire_coroutine_threadsafe(
|
||||||
"""Discover platform job."""
|
async_load_platform(hass, component, platform,
|
||||||
|
discovered, hass_config), hass.loop)
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_load_platform(hass, component, platform, discovered=None,
|
||||||
|
hass_config=None):
|
||||||
|
"""Load a component and platform dynamically.
|
||||||
|
|
||||||
|
Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be
|
||||||
|
fired to load the platform. The event will contain:
|
||||||
|
{ ATTR_SERVICE = LOAD_PLATFORM + '.' + <<component>>
|
||||||
|
ATTR_PLATFORM = <<platform>>
|
||||||
|
ATTR_DISCOVERED = <<discovery info>> }
|
||||||
|
|
||||||
|
Use `listen_platform` to register a callback for these events.
|
||||||
|
|
||||||
|
Warning: Do not yield from this inside a setup method to avoid a dead lock.
|
||||||
|
Use `hass.loop.create_task(async_load_platform(..))` instead.
|
||||||
|
|
||||||
|
This method is a coroutine.
|
||||||
|
"""
|
||||||
|
did_lock = False
|
||||||
|
if hasattr(hass, 'setup_lock') and hass.setup_lock.locked():
|
||||||
|
did_lock = True
|
||||||
|
yield from hass.setup_lock.acquire()
|
||||||
|
|
||||||
|
try:
|
||||||
# No need to fire event if we could not setup component
|
# No need to fire event if we could not setup component
|
||||||
if not bootstrap.setup_component(hass, component, hass_config):
|
res = yield from bootstrap.async_setup_component(
|
||||||
|
hass, component, hass_config)
|
||||||
|
finally:
|
||||||
|
if did_lock:
|
||||||
|
hass.setup_lock.release()
|
||||||
|
|
||||||
|
if not res:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -101,6 +136,4 @@ def load_platform(hass, component, platform, discovered=None,
|
|||||||
if discovered is not None:
|
if discovered is not None:
|
||||||
data[ATTR_DISCOVERED] = discovered
|
data[ATTR_DISCOVERED] = discovered
|
||||||
|
|
||||||
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data)
|
hass.bus.async_fire(EVENT_PLATFORM_DISCOVERED, data)
|
||||||
|
|
||||||
hass.add_job(discover_platform)
|
|
||||||
|
@ -348,6 +348,16 @@ def patch_yaml_files(files_dict, endswith=True):
|
|||||||
return patch.object(yaml, 'open', mock_open_f, create=True)
|
return patch.object(yaml, 'open', mock_open_f, create=True)
|
||||||
|
|
||||||
|
|
||||||
|
def mock_coro(return_value=None):
|
||||||
|
"""Helper method to return a coro that returns a value."""
|
||||||
|
@asyncio.coroutine
|
||||||
|
def coro():
|
||||||
|
"""Fake coroutine."""
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
return coro
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def assert_setup_component(count, domain=None):
|
def assert_setup_component(count, domain=None):
|
||||||
"""Collect valid configuration from setup_component.
|
"""Collect valid configuration from setup_component.
|
||||||
|
@ -3,8 +3,10 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
from homeassistant import loader, bootstrap
|
from homeassistant import loader, bootstrap
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
|
from homeassistant.util.async import run_coroutine_threadsafe
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant, MockModule, MockPlatform
|
from tests.common import (
|
||||||
|
get_test_home_assistant, MockModule, MockPlatform, mock_coro)
|
||||||
|
|
||||||
|
|
||||||
class TestHelpersDiscovery:
|
class TestHelpersDiscovery:
|
||||||
@ -12,7 +14,7 @@ class TestHelpersDiscovery:
|
|||||||
|
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant(1)
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
def teardown_method(self, method):
|
def teardown_method(self, method):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
@ -55,7 +57,8 @@ class TestHelpersDiscovery:
|
|||||||
assert ['test service', 'another service'] == [info[0] for info
|
assert ['test service', 'another service'] == [info[0] for info
|
||||||
in calls_multi]
|
in calls_multi]
|
||||||
|
|
||||||
@patch('homeassistant.bootstrap.setup_component')
|
@patch('homeassistant.bootstrap.async_setup_component',
|
||||||
|
return_value=mock_coro(True)())
|
||||||
def test_platform(self, mock_setup_component):
|
def test_platform(self, mock_setup_component):
|
||||||
"""Test discover platform method."""
|
"""Test discover platform method."""
|
||||||
calls = []
|
calls = []
|
||||||
@ -91,7 +94,17 @@ class TestHelpersDiscovery:
|
|||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
def test_circular_import(self):
|
def test_circular_import(self):
|
||||||
"""Test we don't break doing circular import."""
|
"""Test we don't break doing circular import.
|
||||||
|
|
||||||
|
This test will have test_component discover the switch.test_circular
|
||||||
|
component while setting up.
|
||||||
|
|
||||||
|
The supplied config will load test_component and will load
|
||||||
|
switch.test_circular.
|
||||||
|
|
||||||
|
That means that after startup, we will have test_component and switch
|
||||||
|
setup. The test_circular platform has been loaded twice.
|
||||||
|
"""
|
||||||
component_calls = []
|
component_calls = []
|
||||||
platform_calls = []
|
platform_calls = []
|
||||||
|
|
||||||
@ -122,9 +135,17 @@ class TestHelpersDiscovery:
|
|||||||
'platform': 'test_circular',
|
'platform': 'test_circular',
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# We wait for the setup_lock to finish
|
||||||
|
run_coroutine_threadsafe(
|
||||||
|
self.hass.setup_lock.acquire(), self.hass.loop).result()
|
||||||
|
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
# test_component will only be setup once
|
||||||
|
assert len(component_calls) == 1
|
||||||
|
# The platform will be setup once via the config in `setup_component`
|
||||||
|
# and once via the discovery inside test_component.
|
||||||
|
assert len(platform_calls) == 2
|
||||||
assert 'test_component' in self.hass.config.components
|
assert 'test_component' in self.hass.config.components
|
||||||
assert 'switch' in self.hass.config.components
|
assert 'switch' in self.hass.config.components
|
||||||
assert len(component_calls) == 1
|
|
||||||
assert len(platform_calls) == 1
|
|
||||||
|
@ -13,7 +13,8 @@ from homeassistant.helpers import discovery
|
|||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
get_test_home_assistant, MockPlatform, MockModule, fire_time_changed)
|
get_test_home_assistant, MockPlatform, MockModule, fire_time_changed,
|
||||||
|
mock_coro)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DOMAIN = "test_domain"
|
DOMAIN = "test_domain"
|
||||||
@ -226,7 +227,8 @@ class TestHelpersEntityComponent(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('homeassistant.helpers.entity_component.EntityComponent'
|
@patch('homeassistant.helpers.entity_component.EntityComponent'
|
||||||
'._async_setup_platform')
|
'._async_setup_platform')
|
||||||
@patch('homeassistant.bootstrap.setup_component', return_value=True)
|
@patch('homeassistant.bootstrap.async_setup_component',
|
||||||
|
return_value=mock_coro(True)())
|
||||||
def test_setup_does_discovery(self, mock_setup_component, mock_setup):
|
def test_setup_does_discovery(self, mock_setup_component, mock_setup):
|
||||||
"""Test setup for discovery."""
|
"""Test setup for discovery."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user