Further integration load cleanups (#23104)

* Further integration load cleanups

* Fix tests

* Unflake MQTT vacuum command test
This commit is contained in:
Paulus Schoutsen 2019-04-14 19:07:05 -07:00 committed by GitHub
parent 930f75220c
commit d722f4d64a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 160 additions and 237 deletions

View file

@ -290,23 +290,27 @@ class ConfigEntry:
self._async_cancel_retry_setup = None self._async_cancel_retry_setup = None
async def async_setup( async def async_setup(
self, hass: HomeAssistant, *, component=None, tries=0) -> None: self, hass: HomeAssistant, *,
integration: Optional[loader.Integration] = None, tries=0) -> None:
"""Set up an entry.""" """Set up an entry."""
if component is None: if integration is None:
component = getattr(hass.components, self.domain) integration = await loader.async_get_integration(hass, self.domain)
component = integration.get_component()
# Perform migration # Perform migration
if component.DOMAIN == self.domain: if integration.domain == self.domain:
if not await self.async_migrate(hass): if not await self.async_migrate(hass):
self.state = ENTRY_STATE_MIGRATION_ERROR self.state = ENTRY_STATE_MIGRATION_ERROR
return return
try: try:
result = await component.async_setup_entry(hass, self) result = await component.async_setup_entry( # type: ignore
hass, self)
if not isinstance(result, bool): if not isinstance(result, bool):
_LOGGER.error('%s.async_setup_entry did not return boolean', _LOGGER.error('%s.async_setup_entry did not return boolean',
component.DOMAIN) integration.domain)
result = False result = False
except ConfigEntryNotReady: except ConfigEntryNotReady:
self.state = ENTRY_STATE_SETUP_RETRY self.state = ENTRY_STATE_SETUP_RETRY
@ -319,18 +323,19 @@ class ConfigEntry:
async def setup_again(now): async def setup_again(now):
"""Run setup again.""" """Run setup again."""
self._async_cancel_retry_setup = None self._async_cancel_retry_setup = None
await self.async_setup(hass, component=component, tries=tries) await self.async_setup(
hass, integration=integration, tries=tries)
self._async_cancel_retry_setup = \ self._async_cancel_retry_setup = \
hass.helpers.event.async_call_later(wait_time, setup_again) hass.helpers.event.async_call_later(wait_time, setup_again)
return return
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up entry %s for %s', _LOGGER.exception('Error setting up entry %s for %s',
self.title, component.DOMAIN) self.title, integration.domain)
result = False result = False
# Only store setup result as state if it was not forwarded. # Only store setup result as state if it was not forwarded.
if self.domain != component.DOMAIN: if self.domain != integration.domain:
return return
if result: if result:
@ -338,15 +343,17 @@ class ConfigEntry:
else: else:
self.state = ENTRY_STATE_SETUP_ERROR self.state = ENTRY_STATE_SETUP_ERROR
async def async_unload(self, hass, *, component=None) -> bool: async def async_unload(self, hass, *, integration=None) -> bool:
"""Unload an entry. """Unload an entry.
Returns if unload is possible and was successful. Returns if unload is possible and was successful.
""" """
if component is None: if integration is None:
component = getattr(hass.components, self.domain) integration = await loader.async_get_integration(hass, self.domain)
if component.DOMAIN == self.domain: component = integration.get_component()
if integration.domain == self.domain:
if self.state in UNRECOVERABLE_STATES: if self.state in UNRECOVERABLE_STATES:
return False return False
@ -361,7 +368,7 @@ class ConfigEntry:
supports_unload = hasattr(component, 'async_unload_entry') supports_unload = hasattr(component, 'async_unload_entry')
if not supports_unload: if not supports_unload:
if component.DOMAIN == self.domain: if integration.domain == self.domain:
self.state = ENTRY_STATE_FAILED_UNLOAD self.state = ENTRY_STATE_FAILED_UNLOAD
return False return False
@ -371,27 +378,29 @@ class ConfigEntry:
assert isinstance(result, bool) assert isinstance(result, bool)
# Only adjust state if we unloaded the component # Only adjust state if we unloaded the component
if result and component.DOMAIN == self.domain: if result and integration.domain == self.domain:
self.state = ENTRY_STATE_NOT_LOADED self.state = ENTRY_STATE_NOT_LOADED
return result return result
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error unloading entry %s for %s', _LOGGER.exception('Error unloading entry %s for %s',
self.title, component.DOMAIN) self.title, integration.domain)
if component.DOMAIN == self.domain: if integration.domain == self.domain:
self.state = ENTRY_STATE_FAILED_UNLOAD self.state = ENTRY_STATE_FAILED_UNLOAD
return False return False
async def async_remove(self, hass: HomeAssistant) -> None: async def async_remove(self, hass: HomeAssistant) -> None:
"""Invoke remove callback on component.""" """Invoke remove callback on component."""
component = getattr(hass.components, self.domain) integration = await loader.async_get_integration(hass, self.domain)
component = integration.get_component()
if not hasattr(component, 'async_remove_entry'): if not hasattr(component, 'async_remove_entry'):
return return
try: try:
await component.async_remove_entry(hass, self) await component.async_remove_entry( # type: ignore
hass, self)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error calling entry remove callback %s for %s', _LOGGER.exception('Error calling entry remove callback %s for %s',
self.title, component.DOMAIN) self.title, integration.domain)
async def async_migrate(self, hass: HomeAssistant) -> bool: async def async_migrate(self, hass: HomeAssistant) -> bool:
"""Migrate an entry. """Migrate an entry.
@ -624,7 +633,7 @@ class ConfigEntries:
self._async_schedule_save() self._async_schedule_save()
async def async_forward_entry_setup(self, entry, component): async def async_forward_entry_setup(self, entry, domain):
"""Forward the setup of an entry to a different component. """Forward the setup of an entry to a different component.
By default an entry is setup with the component it belongs to. If that By default an entry is setup with the component it belongs to. If that
@ -635,24 +644,26 @@ class ConfigEntries:
setup of a component, because it can cause a deadlock. setup of a component, because it can cause a deadlock.
""" """
# Setup Component if not set up yet # Setup Component if not set up yet
if component not in self.hass.config.components: if domain not in self.hass.config.components:
result = await async_setup_component( result = await async_setup_component(
self.hass, component, self._hass_config) self.hass, domain, self._hass_config)
if not result: if not result:
return False return False
await entry.async_setup( integration = await loader.async_get_integration(self.hass, domain)
self.hass, component=getattr(self.hass.components, component))
async def async_forward_entry_unload(self, entry, component): await entry.async_setup(self.hass, integration=integration)
async def async_forward_entry_unload(self, entry, domain):
"""Forward the unloading of an entry to a different component.""" """Forward the unloading of an entry to a different component."""
# It was never loaded. # It was never loaded.
if component not in self.hass.config.components: if domain not in self.hass.config.components:
return True return True
return await entry.async_unload( integration = await loader.async_get_integration(self.hass, domain)
self.hass, component=getattr(self.hass.components, component))
return await entry.async_unload(self.hass, integration=integration)
async def _async_finish_flow(self, flow, result): async def _async_finish_flow(self, flow, result):
"""Finish a config flow and add an entry.""" """Finish a config flow and add an entry."""
@ -688,10 +699,10 @@ class ConfigEntries:
Handler key is the domain of the component that we want to set up. Handler key is the domain of the component that we want to set up.
""" """
integration = await loader.async_get_integration( try:
self.hass, handler_key) integration = await loader.async_get_integration(
self.hass, handler_key)
if integration is None: except loader.IntegrationNotFound:
raise data_entry_flow.UnknownHandler raise data_entry_flow.UnknownHandler
handler = HANDLERS.get(handler_key) handler = HANDLERS.get(handler_key)

View file

@ -22,8 +22,6 @@ from typing import (
Dict Dict
) )
from homeassistant.const import PLATFORM_FORMAT
# Typing imports that create a circular dependency # Typing imports that create a circular dependency
# pylint: disable=using-constant-test,unused-import # pylint: disable=using-constant-test,unused-import
if TYPE_CHECKING: if TYPE_CHECKING:
@ -38,7 +36,7 @@ DEPENDENCY_BLACKLIST = {'config'}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_KEY = 'components' DATA_COMPONENTS = 'components'
DATA_INTEGRATIONS = 'integrations' DATA_INTEGRATIONS = 'integrations'
PACKAGE_CUSTOM_COMPONENTS = 'custom_components' PACKAGE_CUSTOM_COMPONENTS = 'custom_components'
PACKAGE_BUILTIN = 'homeassistant.components' PACKAGE_BUILTIN = 'homeassistant.components'
@ -117,14 +115,14 @@ class Integration:
def get_component(self) -> ModuleType: def get_component(self) -> ModuleType:
"""Return the component.""" """Return the component."""
cache = self.hass.data.setdefault(DATA_KEY, {}) cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
if self.domain not in cache: if self.domain not in cache:
cache[self.domain] = importlib.import_module(self.pkg_path) cache[self.domain] = importlib.import_module(self.pkg_path)
return cache[self.domain] # type: ignore return cache[self.domain] # type: ignore
def get_platform(self, platform_name: str) -> ModuleType: def get_platform(self, platform_name: str) -> ModuleType:
"""Return a platform for an integration.""" """Return a platform for an integration."""
cache = self.hass.data.setdefault(DATA_KEY, {}) cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
full_name = "{}.{}".format(self.domain, platform_name) full_name = "{}.{}".format(self.domain, platform_name)
if full_name not in cache: if full_name not in cache:
cache[full_name] = importlib.import_module( cache[full_name] = importlib.import_module(
@ -212,68 +210,6 @@ class CircularDependency(LoaderError):
self.to_domain = to_domain self.to_domain = to_domain
def set_component(hass, # type: HomeAssistant
comp_name: str, component: Optional[ModuleType]) -> None:
"""Set a component in the cache.
Async friendly.
"""
cache = hass.data.setdefault(DATA_KEY, {})
cache[comp_name] = component
def get_platform(hass, # type: HomeAssistant
domain: str, platform_name: str) -> Optional[ModuleType]:
"""Try to load specified platform.
Example invocation: get_platform(hass, 'light', 'hue')
Async friendly.
"""
# If the platform has a component, we will limit the platform loading path
# to be the same source (custom/built-in).
component = _load_file(hass, platform_name, LOOKUP_PATHS)
# Until we have moved all platforms under their component/own folder, it
# can be that the component is None.
if component is not None:
base_paths = [component.__name__.rsplit('.', 1)[0]]
else:
base_paths = LOOKUP_PATHS
platform = _load_file(
hass, PLATFORM_FORMAT.format(domain=domain, platform=platform_name),
base_paths)
if platform is not None:
return platform
# Legacy platform check for custom: custom_components/light/hue.py
# Only check if the component was also in custom components.
if component is None or base_paths[0] == PACKAGE_CUSTOM_COMPONENTS:
platform = _load_file(
hass,
PLATFORM_FORMAT.format(domain=platform_name, platform=domain),
[PACKAGE_CUSTOM_COMPONENTS]
)
if platform is None:
if component is None:
extra = ""
else:
extra = " Search path was limited to path of component: {}".format(
base_paths[0])
_LOGGER.error("Unable to find platform %s.%s", platform_name, extra)
return None
_LOGGER.error(
"Integrations need to be in their own folder. Change %s/%s.py to "
"%s/%s.py. This will stop working soon.",
domain, platform_name, platform_name, domain)
return platform
def get_component(hass, # type: HomeAssistant def get_component(hass, # type: HomeAssistant
comp_or_platform: str) -> Optional[ModuleType]: comp_or_platform: str) -> Optional[ModuleType]:
"""Try to load specified component. """Try to load specified component.
@ -298,15 +234,15 @@ def _load_file(hass, # type: HomeAssistant
Async friendly. Async friendly.
""" """
try: try:
return hass.data[DATA_KEY][comp_or_platform] # type: ignore return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore
except KeyError: except KeyError:
pass pass
cache = hass.data.get(DATA_KEY) cache = hass.data.get(DATA_COMPONENTS)
if cache is None: if cache is None:
if not _async_mount_config_dir(hass): if not _async_mount_config_dir(hass):
return None return None
cache = hass.data[DATA_KEY] = {} cache = hass.data[DATA_COMPONENTS] = {}
for path in ('{}.{}'.format(base, comp_or_platform) for path in ('{}.{}'.format(base, comp_or_platform)
for base in base_paths): for base in base_paths):
@ -392,9 +328,18 @@ class Components:
def __getattr__(self, comp_name: str) -> ModuleWrapper: def __getattr__(self, comp_name: str) -> ModuleWrapper:
"""Fetch a component.""" """Fetch a component."""
component = get_component(self._hass, comp_name) # Test integration cache
integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name)
if integration:
component = integration.get_component()
else:
# Fallback to importing old-school
component = _load_file(self._hass, comp_name, LOOKUP_PATHS)
if component is None: if component is None:
raise ImportError('Unable to load {}'.format(comp_name)) raise ImportError('Unable to load {}'.format(comp_name))
wrapped = ModuleWrapper(self._hass, component) wrapped = ModuleWrapper(self._hass, component)
setattr(self, comp_name, wrapped) setattr(self, comp_name, wrapped)
return wrapped return wrapped

View file

@ -168,12 +168,11 @@ async def _async_setup_component(hass: core.HomeAssistant,
if result is not True: if result is not True:
log_error("Component {!r} did not return boolean if setup was " log_error("Component {!r} did not return boolean if setup was "
"successful. Disabling component.".format(domain)) "successful. Disabling component.".format(domain))
loader.set_component(hass, domain, None)
return False return False
if hass.config_entries: if hass.config_entries:
for entry in hass.config_entries.async_entries(domain): for entry in hass.config_entries.async_entries(domain):
await entry.async_setup(hass, component=component) await entry.async_setup(hass, integration=integration)
hass.config.components.add(component.DOMAIN) # type: ignore hass.config.components.add(component.DOMAIN) # type: ignore

View file

@ -4,6 +4,7 @@ import functools as ft
import json import json
import logging import logging
import os import os
import uuid
import sys import sys
import threading import threading
@ -607,7 +608,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
connection_class=config_entries.CONN_CLASS_UNKNOWN): connection_class=config_entries.CONN_CLASS_UNKNOWN):
"""Initialize a mock config entry.""" """Initialize a mock config entry."""
kwargs = { kwargs = {
'entry_id': entry_id or 'mock-id', 'entry_id': entry_id or uuid.uuid4().hex,
'domain': domain, 'domain': domain,
'data': data or {}, 'data': data or {},
'options': options, 'options': options,
@ -911,7 +912,7 @@ def mock_integration(hass, module):
hass.data.setdefault( hass.data.setdefault(
loader.DATA_INTEGRATIONS, {} loader.DATA_INTEGRATIONS, {}
)[module.DOMAIN] = integration )[module.DOMAIN] = integration
hass.data.setdefault(loader.DATA_KEY, {})[module.DOMAIN] = module hass.data.setdefault(loader.DATA_COMPONENTS, {})[module.DOMAIN] = module
def mock_entity_platform(hass, platform_path, module): def mock_entity_platform(hass, platform_path, module):
@ -921,8 +922,8 @@ def mock_entity_platform(hass, platform_path, module):
hue.light. hue.light.
""" """
domain, platform_name = platform_path.split('.') domain, platform_name = platform_path.split('.')
integration_cache = hass.data.setdefault(loader.DATA_KEY, {}) integration_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
module_cache = hass.data.setdefault(loader.DATA_KEY, {}) module_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
if platform_name not in integration_cache: if platform_name not in integration_cache:
mock_integration(hass, MockModule(platform_name)) mock_integration(hass, MockModule(platform_name))

View file

@ -12,15 +12,15 @@ from homeassistant.config_entries import HANDLERS
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components.config import config_entries from homeassistant.components.config import config_entries
from homeassistant.loader import set_component
from tests.common import MockConfigEntry, MockModule, mock_coro_func from tests.common import (
MockConfigEntry, MockModule, mock_coro_func, mock_integration)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_test_component(hass): def mock_test_component(hass):
"""Ensure a component called 'test' exists.""" """Ensure a component called 'test' exists."""
set_component(hass, 'test', MockModule('test')) mock_integration(hass, MockModule('test'))
@pytest.fixture @pytest.fixture
@ -244,8 +244,8 @@ def test_abort(hass, client):
@asyncio.coroutine @asyncio.coroutine
def test_create_account(hass, client): def test_create_account(hass, client):
"""Test a flow that creates an account.""" """Test a flow that creates an account."""
set_component( mock_integration(
hass, 'test', hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@ -283,8 +283,8 @@ def test_create_account(hass, client):
@asyncio.coroutine @asyncio.coroutine
def test_two_step_flow(hass, client): def test_two_step_flow(hass, client):
"""Test we can finish a two step flow.""" """Test we can finish a two step flow."""
set_component( mock_integration(
hass, 'test', hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@ -349,8 +349,8 @@ def test_two_step_flow(hass, client):
async def test_continue_flow_unauth(hass, client, hass_admin_user): async def test_continue_flow_unauth(hass, client, hass_admin_user):
"""Test we can't finish a two step flow.""" """Test we can't finish a two step flow."""
set_component( mock_integration(
hass, 'test', hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@ -562,8 +562,8 @@ async def test_options_flow(hass, client):
async def test_two_step_options_flow(hass, client): async def test_two_step_options_flow(hass, client):
"""Test we can finish a two step options flow.""" """Test we can finish a two step options flow."""
set_component( mock_integration(
hass, 'test', hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):

View file

@ -136,9 +136,10 @@ async def test_all_commands(hass, mock_publish):
entity_id='vacuum.mqtttest') entity_id='vacuum.mqtttest')
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
mock_publish.async_publish.assert_called_once_with( assert json.loads(mock_publish.async_publish.mock_calls[-1][1][1]) == {
'vacuum/send_command', '{"command": "44 FE 93", "key": "value"}', "command": "44 FE 93",
0, False) "key": "value"
}
async def test_status(hass, mock_publish): async def test_status(hass, mock_publish):

View file

@ -7,7 +7,7 @@ from homeassistant import core, loader
from homeassistant.components import switch from homeassistant.components import switch
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant, mock_entity_platform
from tests.components.switch import common from tests.components.switch import common
@ -80,7 +80,7 @@ class TestSwitch(unittest.TestCase):
test_platform = loader.get_component(self.hass, 'test.switch') test_platform = loader.get_component(self.hass, 'test.switch')
test_platform.init(True) test_platform.init(True)
loader.set_component(self.hass, 'switch.test2', test_platform) mock_entity_platform(self.hass, 'switch.test2', test_platform)
test_platform.init(False) test_platform.init(False)
assert setup_component( assert setup_component(

View file

@ -3,9 +3,10 @@ from unittest.mock import patch, Mock
import pytest import pytest
from homeassistant import config_entries, data_entry_flow, loader, setup from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
from tests.common import MockConfigEntry, MockModule, mock_coro from tests.common import (
MockConfigEntry, MockModule, mock_coro, mock_integration)
@pytest.fixture @pytest.fixture
@ -101,7 +102,7 @@ async def test_discovery_confirmation(hass, discovery_flow_conf):
async def test_multiple_discoveries(hass, discovery_flow_conf): async def test_multiple_discoveries(hass, discovery_flow_conf):
"""Test we only create one instance for multiple discoveries.""" """Test we only create one instance for multiple discoveries."""
loader.set_component(hass, 'test', MockModule('test')) mock_integration(hass, MockModule('test'))
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
'test', context={'source': config_entries.SOURCE_DISCOVERY}, data={}) 'test', context={'source': config_entries.SOURCE_DISCOVERY}, data={})
@ -115,7 +116,7 @@ async def test_multiple_discoveries(hass, discovery_flow_conf):
async def test_only_one_in_progress(hass, discovery_flow_conf): async def test_only_one_in_progress(hass, discovery_flow_conf):
"""Test a user initialized one will finish and cancel discovered one.""" """Test a user initialized one will finish and cancel discovered one."""
loader.set_component(hass, 'test', MockModule('test')) mock_integration(hass, MockModule('test'))
# Discovery starts flow # Discovery starts flow
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -202,7 +203,7 @@ async def test_webhook_create_cloudhook(hass, webhook_flow_conf):
async_setup_entry = Mock(return_value=mock_coro(True)) async_setup_entry = Mock(return_value=mock_coro(True))
async_unload_entry = Mock(return_value=mock_coro(True)) async_unload_entry = Mock(return_value=mock_coro(True))
loader.set_component(hass, 'test_single', MockModule( mock_integration(hass, MockModule(
'test_single', 'test_single',
async_setup_entry=async_setup_entry, async_setup_entry=async_setup_entry,
async_unload_entry=async_unload_entry, async_unload_entry=async_unload_entry,

View file

@ -3,13 +3,14 @@ from unittest.mock import patch
import pytest import pytest
from homeassistant import loader, setup from homeassistant import setup
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from tests.common import ( from tests.common import (
get_test_home_assistant, MockModule, MockPlatform, mock_coro) get_test_home_assistant, MockModule, MockPlatform, mock_coro,
mock_integration, mock_entity_platform)
class TestHelpersDiscovery: class TestHelpersDiscovery:
@ -128,17 +129,17 @@ class TestHelpersDiscovery:
"""Set up mock platform.""" """Set up mock platform."""
platform_calls.append('disc' if discovery_info else 'component') platform_calls.append('disc' if discovery_info else 'component')
loader.set_component( mock_integration(
self.hass, 'test_component', self.hass,
MockModule('test_component', setup=component_setup)) MockModule('test_component', setup=component_setup))
# dependencies are only set in component level # dependencies are only set in component level
# since we are using manifest to hold them # since we are using manifest to hold them
loader.set_component( mock_integration(
self.hass, 'test_circular', self.hass,
MockModule('test_circular', dependencies=['test_component'])) MockModule('test_circular', dependencies=['test_component']))
loader.set_component( mock_entity_platform(
self.hass, 'test_circular.switch', self.hass, 'switch.test_circular',
MockPlatform(setup_platform)) MockPlatform(setup_platform))
setup.setup_component(self.hass, 'test_component', { setup.setup_component(self.hass, 'test_component', {
@ -180,12 +181,12 @@ class TestHelpersDiscovery:
component_calls.append(1) component_calls.append(1)
return True return True
loader.set_component( mock_integration(
self.hass, 'test_component1', self.hass,
MockModule('test_component1', setup=component1_setup)) MockModule('test_component1', setup=component1_setup))
loader.set_component( mock_integration(
self.hass, 'test_component2', self.hass,
MockModule('test_component2', setup=component2_setup)) MockModule('test_component2', setup=component2_setup))
@callback @callback

View file

@ -10,7 +10,6 @@ from datetime import timedelta
import pytest import pytest
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.loader as loader
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.components import group from homeassistant.components import group
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
@ -226,9 +225,8 @@ def test_platform_not_ready(hass):
"""Test that we retry when platform not ready.""" """Test that we retry when platform not ready."""
platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady, platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady,
None]) None])
loader.set_component(hass, 'mod1', mock_integration(hass, MockModule('mod1'))
MockModule('mod1')) mock_entity_platform(hass, 'test_domain.mod1',
loader.set_component(hass, 'mod1.test_domain',
MockPlatform(platform1_setup)) MockPlatform(platform1_setup))
component = EntityComponent(_LOGGER, DOMAIN, hass) component = EntityComponent(_LOGGER, DOMAIN, hass)
@ -326,12 +324,10 @@ def test_setup_dependencies_platform(hass):
We're explictely testing that we process dependencies even if a component We're explictely testing that we process dependencies even if a component
with the same name has already been loaded. with the same name has already been loaded.
""" """
loader.set_component(hass, 'test_component', mock_integration(hass, MockModule('test_component',
MockModule('test_component', dependencies=['test_component2']))
dependencies=['test_component2'])) mock_integration(hass, MockModule('test_component2'))
loader.set_component(hass, 'test_component2', mock_entity_platform(hass, 'test_domain.test_component', MockPlatform())
MockModule('test_component2'))
loader.set_component(hass, 'test_component.test_domain', MockPlatform())
component = EntityComponent(_LOGGER, DOMAIN, hass) component = EntityComponent(_LOGGER, DOMAIN, hass)

View file

@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from homeassistant import config_entries, loader, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -49,8 +49,8 @@ async def test_call_setup_entry(hass):
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
mock_migrate_entry = MagicMock(return_value=mock_coro(True)) mock_migrate_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry, MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry)) async_migrate_entry=mock_migrate_entry))
@ -70,8 +70,8 @@ async def test_call_async_migrate_entry(hass):
mock_migrate_entry = MagicMock(return_value=mock_coro(True)) mock_migrate_entry = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry, MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry)) async_migrate_entry=mock_migrate_entry))
@ -91,8 +91,8 @@ async def test_call_async_migrate_entry_failure_false(hass):
mock_migrate_entry = MagicMock(return_value=mock_coro(False)) mock_migrate_entry = MagicMock(return_value=mock_coro(False))
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry, MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry)) async_migrate_entry=mock_migrate_entry))
@ -113,8 +113,8 @@ async def test_call_async_migrate_entry_failure_exception(hass):
return_value=mock_coro(exception=Exception)) return_value=mock_coro(exception=Exception))
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry, MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry)) async_migrate_entry=mock_migrate_entry))
@ -135,8 +135,8 @@ async def test_call_async_migrate_entry_failure_not_bool(hass):
return_value=mock_coro()) return_value=mock_coro())
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry, MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry)) async_migrate_entry=mock_migrate_entry))
@ -155,8 +155,8 @@ async def test_call_async_migrate_entry_failure_not_supported(hass):
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry)) MockModule('comp', async_setup_entry=mock_setup_entry))
result = await async_setup_component(hass, 'comp', {}) result = await async_setup_component(hass, 'comp', {})
@ -265,7 +265,7 @@ async def test_remove_entry_handles_callback_error(hass, manager):
mock_unload_entry = MagicMock(return_value=mock_coro(True)) mock_unload_entry = MagicMock(return_value=mock_coro(True))
mock_remove_entry = MagicMock( mock_remove_entry = MagicMock(
side_effect=lambda *args, **kwargs: mock_coro()) side_effect=lambda *args, **kwargs: mock_coro())
loader.set_component(hass, 'test', MockModule( mock_integration(hass, MockModule(
'test', 'test',
async_setup_entry=mock_setup_entry, async_setup_entry=mock_setup_entry,
async_unload_entry=mock_unload_entry, async_unload_entry=mock_unload_entry,
@ -304,13 +304,12 @@ def test_remove_entry_raises(hass, manager):
"""Mock unload entry function.""" """Mock unload entry function."""
raise Exception("BROKEN") raise Exception("BROKEN")
loader.set_component( mock_integration(hass, MockModule(
hass, 'test', 'comp', async_unload_entry=mock_unload_entry))
MockModule('comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
MockConfigEntry( MockConfigEntry(
domain='test', domain='comp',
entry_id='test2', entry_id='test2',
state=config_entries.ENTRY_STATE_LOADED state=config_entries.ENTRY_STATE_LOADED
).add_to_manager(manager) ).add_to_manager(manager)
@ -330,15 +329,14 @@ def test_remove_entry_raises(hass, manager):
@asyncio.coroutine @asyncio.coroutine
def test_remove_entry_if_not_loaded(hass, manager): def test_remove_entry_if_not_loaded(hass, manager):
"""Test that we can remove an entry.""" """Test that we can remove an entry that is not loaded."""
mock_unload_entry = MagicMock(return_value=mock_coro(True)) mock_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(hass, MockModule(
hass, 'test', 'comp', async_unload_entry=mock_unload_entry))
MockModule('comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager) MockConfigEntry(domain='comp', entry_id='test2').add_to_manager(manager)
MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
assert [item.entry_id for item in manager.async_entries()] == \ assert [item.entry_id for item in manager.async_entries()] == \
@ -352,7 +350,7 @@ def test_remove_entry_if_not_loaded(hass, manager):
assert [item.entry_id for item in manager.async_entries()] == \ assert [item.entry_id for item in manager.async_entries()] == \
['test1', 'test3'] ['test1', 'test3']
assert len(mock_unload_entry.mock_calls) == 1 assert len(mock_unload_entry.mock_calls) == 0
@asyncio.coroutine @asyncio.coroutine
@ -360,8 +358,8 @@ def test_add_entry_calls_setup_entry(hass, manager):
"""Test we call setup_config_entry.""" """Test we call setup_config_entry."""
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'comp', hass,
MockModule('comp', async_setup_entry=mock_setup_entry)) MockModule('comp', async_setup_entry=mock_setup_entry))
class TestFlow(config_entries.ConfigFlow): class TestFlow(config_entries.ConfigFlow):
@ -416,9 +414,8 @@ def test_domains_gets_uniques(manager):
async def test_saving_and_loading(hass): async def test_saving_and_loading(hass):
"""Test that we're saving and loading correctly.""" """Test that we're saving and loading correctly."""
loader.set_component( mock_integration(hass, MockModule(
hass, 'test', 'test', async_setup_entry=lambda *args: mock_coro(True)))
MockModule('test', async_setup_entry=lambda *args: mock_coro(True)))
class TestFlow(config_entries.ConfigFlow): class TestFlow(config_entries.ConfigFlow):
VERSION = 5 VERSION = 5
@ -480,13 +477,13 @@ async def test_forward_entry_sets_up_component(hass):
entry = MockConfigEntry(domain='original') entry = MockConfigEntry(domain='original')
mock_original_setup_entry = MagicMock(return_value=mock_coro(True)) mock_original_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'original', hass,
MockModule('original', async_setup_entry=mock_original_setup_entry)) MockModule('original', async_setup_entry=mock_original_setup_entry))
mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True)) mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component( mock_integration(
hass, 'forwarded', hass,
MockModule('forwarded', async_setup_entry=mock_forwarded_setup_entry)) MockModule('forwarded', async_setup_entry=mock_forwarded_setup_entry))
await hass.config_entries.async_forward_entry_setup(entry, 'forwarded') await hass.config_entries.async_forward_entry_setup(entry, 'forwarded')
@ -500,7 +497,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
mock_setup = MagicMock(return_value=mock_coro(False)) mock_setup = MagicMock(return_value=mock_coro(False))
mock_setup_entry = MagicMock() mock_setup_entry = MagicMock()
hass, loader.set_component(hass, 'forwarded', MockModule( mock_integration(hass, MockModule(
'forwarded', 'forwarded',
async_setup=mock_setup, async_setup=mock_setup,
async_setup_entry=mock_setup_entry, async_setup_entry=mock_setup_entry,
@ -513,7 +510,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
async def test_discovery_notification(hass): async def test_discovery_notification(hass):
"""Test that we create/dismiss a notification when source is discovery.""" """Test that we create/dismiss a notification when source is discovery."""
loader.set_component(hass, 'test', MockModule('test')) mock_integration(hass, MockModule('test'))
await async_setup_component(hass, 'persistent_notification', {}) await async_setup_component(hass, 'persistent_notification', {})
class TestFlow(config_entries.ConfigFlow): class TestFlow(config_entries.ConfigFlow):
@ -550,7 +547,7 @@ async def test_discovery_notification(hass):
async def test_discovery_notification_not_created(hass): async def test_discovery_notification_not_created(hass):
"""Test that we not create a notification when discovery is aborted.""" """Test that we not create a notification when discovery is aborted."""
loader.set_component(hass, 'test', MockModule('test')) mock_integration(hass, MockModule('test'))
await async_setup_component(hass, 'persistent_notification', {}) await async_setup_component(hass, 'persistent_notification', {})
class TestFlow(config_entries.ConfigFlow): class TestFlow(config_entries.ConfigFlow):
@ -630,8 +627,8 @@ async def test_setup_raise_not_ready(hass, caplog):
entry = MockConfigEntry(domain='test') entry = MockConfigEntry(domain='test')
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
loader.set_component( mock_integration(
hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry)) hass, MockModule('test', async_setup_entry=mock_setup_entry))
with patch('homeassistant.helpers.event.async_call_later') as mock_call: with patch('homeassistant.helpers.event.async_call_later') as mock_call:
await entry.async_setup(hass) await entry.async_setup(hass)
@ -656,8 +653,8 @@ async def test_setup_retrying_during_unload(hass):
entry = MockConfigEntry(domain='test') entry = MockConfigEntry(domain='test')
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
loader.set_component( mock_integration(
hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry)) hass, MockModule('test', async_setup_entry=mock_setup_entry))
with patch('homeassistant.helpers.event.async_call_later') as mock_call: with patch('homeassistant.helpers.event.async_call_later') as mock_call:
await entry.async_setup(hass) await entry.async_setup(hass)
@ -718,7 +715,7 @@ async def test_entry_setup_succeed(hass, manager):
mock_setup = MagicMock(return_value=mock_coro(True)) mock_setup = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_setup=mock_setup, async_setup=mock_setup,
async_setup_entry=mock_setup_entry async_setup_entry=mock_setup_entry
@ -748,7 +745,7 @@ async def test_entry_setup_invalid_state(hass, manager, state):
mock_setup = MagicMock(return_value=mock_coro(True)) mock_setup = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_setup_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_setup=mock_setup, async_setup=mock_setup,
async_setup_entry=mock_setup_entry async_setup_entry=mock_setup_entry
@ -772,7 +769,7 @@ async def test_entry_unload_succeed(hass, manager):
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_unload_entry=async_unload_entry async_unload_entry=async_unload_entry
)) ))
@ -797,7 +794,7 @@ async def test_entry_unload_failed_to_load(hass, manager, state):
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_unload_entry=async_unload_entry async_unload_entry=async_unload_entry
)) ))
@ -821,7 +818,7 @@ async def test_entry_unload_invalid_state(hass, manager, state):
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_unload_entry=async_unload_entry async_unload_entry=async_unload_entry
)) ))
@ -845,7 +842,7 @@ async def test_entry_reload_succeed(hass, manager):
async_setup_entry = MagicMock(return_value=mock_coro(True)) async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_setup=async_setup, async_setup=async_setup,
async_setup_entry=async_setup_entry, async_setup_entry=async_setup_entry,
@ -876,7 +873,7 @@ async def test_entry_reload_not_loaded(hass, manager, state):
async_setup_entry = MagicMock(return_value=mock_coro(True)) async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_setup=async_setup, async_setup=async_setup,
async_setup_entry=async_setup_entry, async_setup_entry=async_setup_entry,
@ -906,7 +903,7 @@ async def test_entry_reload_error(hass, manager, state):
async_setup_entry = MagicMock(return_value=mock_coro(True)) async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True))
loader.set_component(hass, 'comp', MockModule( mock_integration(hass, MockModule(
'comp', 'comp',
async_setup=async_setup, async_setup=async_setup,
async_setup_entry=async_setup_entry, async_setup_entry=async_setup_entry,

View file

@ -8,14 +8,6 @@ from homeassistant.components.hue import light as hue_light
from tests.common import MockModule, async_mock_service, mock_integration from tests.common import MockModule, async_mock_service, mock_integration
def test_set_component(hass):
"""Test if set_component works."""
comp = object()
loader.set_component(hass, 'switch.test_set', comp)
assert loader.get_component(hass, 'switch.test_set') is comp
def test_get_component(hass): def test_get_component(hass):
"""Test if get_component works.""" """Test if get_component works."""
assert http == loader.get_component(hass, 'http') assert http == loader.get_component(hass, 'http')
@ -119,27 +111,6 @@ async def test_log_warning_custom_component(hass, caplog):
assert 'You are using a custom component for test.light' in caplog.text assert 'You are using a custom component for test.light' in caplog.text
async def test_get_platform(hass, caplog):
"""Test get_platform."""
# Test we prefer embedded over normal platforms."""
embedded_platform = loader.get_platform(hass, 'switch', 'test_embedded')
assert embedded_platform.__name__ == \
'custom_components.test_embedded.switch'
caplog.clear()
legacy_platform = loader.get_platform(hass, 'switch', 'test_legacy')
assert legacy_platform.__name__ == 'custom_components.switch.test_legacy'
assert 'Integrations need to be in their own folder.' in caplog.text
async def test_get_platform_enforces_component_path(hass, caplog):
"""Test that existence of a component limits lookup path of platforms."""
assert loader.get_platform(hass, 'comp_path_test', 'hue') is None
assert ('Search path was limited to path of component: '
'homeassistant.components') in caplog.text
async def test_get_integration(hass): async def test_get_integration(hass):
"""Test resolving integration.""" """Test resolving integration."""
integration = await loader.async_get_integration(hass, 'hue') integration = await loader.async_get_integration(hass, 'hue')

View file

@ -2,13 +2,14 @@
import os import os
from unittest.mock import patch, call from unittest.mock import patch, call
from homeassistant import loader, setup from homeassistant import setup
from homeassistant.requirements import ( from homeassistant.requirements import (
CONSTRAINT_FILE, PackageLoadable, async_process_requirements) CONSTRAINT_FILE, PackageLoadable, async_process_requirements)
import pkg_resources import pkg_resources
from tests.common import get_test_home_assistant, MockModule, mock_coro from tests.common import (
get_test_home_assistant, MockModule, mock_coro, mock_integration)
RESOURCE_DIR = os.path.abspath( RESOURCE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'resources')) os.path.join(os.path.dirname(__file__), '..', 'resources'))
@ -43,8 +44,8 @@ class TestRequirements:
mock_venv.return_value = True mock_venv.return_value = True
mock_dirname.return_value = 'ha_package_path' mock_dirname.return_value = 'ha_package_path'
self.hass.config.skip_pip = False self.hass.config.skip_pip = False
loader.set_component( mock_integration(
self.hass, 'comp', self.hass,
MockModule('comp', requirements=['package==0.0.1'])) MockModule('comp', requirements=['package==0.0.1']))
assert setup.setup_component(self.hass, 'comp', {}) assert setup.setup_component(self.hass, 'comp', {})
assert 'comp' in self.hass.config.components assert 'comp' in self.hass.config.components
@ -60,8 +61,8 @@ class TestRequirements:
"""Test requirement installed in deps directory.""" """Test requirement installed in deps directory."""
mock_dirname.return_value = 'ha_package_path' mock_dirname.return_value = 'ha_package_path'
self.hass.config.skip_pip = False self.hass.config.skip_pip = False
loader.set_component( mock_integration(
self.hass, 'comp', self.hass,
MockModule('comp', requirements=['package==0.0.1'])) MockModule('comp', requirements=['package==0.0.1']))
assert setup.setup_component(self.hass, 'comp', {}) assert setup.setup_component(self.hass, 'comp', {})
assert 'comp' in self.hass.config.components assert 'comp' in self.hass.config.components

View file

@ -494,7 +494,6 @@ class TestSetup:
MockModule('disabled_component', setup=lambda hass, config: None)) MockModule('disabled_component', setup=lambda hass, config: None))
assert not setup.setup_component(self.hass, 'disabled_component', {}) assert not setup.setup_component(self.hass, 'disabled_component', {})
assert loader.get_component(self.hass, 'disabled_component') is None
assert 'disabled_component' not in self.hass.config.components assert 'disabled_component' not in self.hass.config.components
self.hass.data.pop(setup.DATA_SETUP) self.hass.data.pop(setup.DATA_SETUP)