diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 917579d6cf1..36f1905cd4e 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -290,23 +290,27 @@ class ConfigEntry: self._async_cancel_retry_setup = None 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.""" - if component is None: - component = getattr(hass.components, self.domain) + if integration is None: + integration = await loader.async_get_integration(hass, self.domain) + + component = integration.get_component() # Perform migration - if component.DOMAIN == self.domain: + if integration.domain == self.domain: if not await self.async_migrate(hass): self.state = ENTRY_STATE_MIGRATION_ERROR return try: - result = await component.async_setup_entry(hass, self) + result = await component.async_setup_entry( # type: ignore + hass, self) if not isinstance(result, bool): _LOGGER.error('%s.async_setup_entry did not return boolean', - component.DOMAIN) + integration.domain) result = False except ConfigEntryNotReady: self.state = ENTRY_STATE_SETUP_RETRY @@ -319,18 +323,19 @@ class ConfigEntry: async def setup_again(now): """Run setup again.""" 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 = \ hass.helpers.event.async_call_later(wait_time, setup_again) return except Exception: # pylint: disable=broad-except _LOGGER.exception('Error setting up entry %s for %s', - self.title, component.DOMAIN) + self.title, integration.domain) result = False # Only store setup result as state if it was not forwarded. - if self.domain != component.DOMAIN: + if self.domain != integration.domain: return if result: @@ -338,15 +343,17 @@ class ConfigEntry: else: 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. Returns if unload is possible and was successful. """ - if component is None: - component = getattr(hass.components, self.domain) + if integration is None: + 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: return False @@ -361,7 +368,7 @@ class ConfigEntry: supports_unload = hasattr(component, 'async_unload_entry') if not supports_unload: - if component.DOMAIN == self.domain: + if integration.domain == self.domain: self.state = ENTRY_STATE_FAILED_UNLOAD return False @@ -371,27 +378,29 @@ class ConfigEntry: assert isinstance(result, bool) # 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 return result except Exception: # pylint: disable=broad-except _LOGGER.exception('Error unloading entry %s for %s', - self.title, component.DOMAIN) - if component.DOMAIN == self.domain: + self.title, integration.domain) + if integration.domain == self.domain: self.state = ENTRY_STATE_FAILED_UNLOAD return False async def async_remove(self, hass: HomeAssistant) -> None: """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'): return try: - await component.async_remove_entry(hass, self) + await component.async_remove_entry( # type: ignore + hass, self) except Exception: # pylint: disable=broad-except _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: """Migrate an entry. @@ -624,7 +633,7 @@ class ConfigEntries: 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. 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 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( - self.hass, component, self._hass_config) + self.hass, domain, self._hass_config) if not result: return False - await entry.async_setup( - self.hass, component=getattr(self.hass.components, component)) + integration = await loader.async_get_integration(self.hass, domain) - 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.""" # It was never loaded. - if component not in self.hass.config.components: + if domain not in self.hass.config.components: return True - return await entry.async_unload( - self.hass, component=getattr(self.hass.components, component)) + integration = await loader.async_get_integration(self.hass, domain) + + return await entry.async_unload(self.hass, integration=integration) async def _async_finish_flow(self, flow, result): """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. """ - integration = await loader.async_get_integration( - self.hass, handler_key) - - if integration is None: + try: + integration = await loader.async_get_integration( + self.hass, handler_key) + except loader.IntegrationNotFound: raise data_entry_flow.UnknownHandler handler = HANDLERS.get(handler_key) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 9e107aa8863..4422312501d 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -22,8 +22,6 @@ from typing import ( Dict ) -from homeassistant.const import PLATFORM_FORMAT - # Typing imports that create a circular dependency # pylint: disable=using-constant-test,unused-import if TYPE_CHECKING: @@ -38,7 +36,7 @@ DEPENDENCY_BLACKLIST = {'config'} _LOGGER = logging.getLogger(__name__) -DATA_KEY = 'components' +DATA_COMPONENTS = 'components' DATA_INTEGRATIONS = 'integrations' PACKAGE_CUSTOM_COMPONENTS = 'custom_components' PACKAGE_BUILTIN = 'homeassistant.components' @@ -117,14 +115,14 @@ class Integration: def get_component(self) -> ModuleType: """Return the component.""" - cache = self.hass.data.setdefault(DATA_KEY, {}) + cache = self.hass.data.setdefault(DATA_COMPONENTS, {}) if self.domain not in cache: cache[self.domain] = importlib.import_module(self.pkg_path) return cache[self.domain] # type: ignore def get_platform(self, platform_name: str) -> ModuleType: """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) if full_name not in cache: cache[full_name] = importlib.import_module( @@ -212,68 +210,6 @@ class CircularDependency(LoaderError): 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 comp_or_platform: str) -> Optional[ModuleType]: """Try to load specified component. @@ -298,15 +234,15 @@ def _load_file(hass, # type: HomeAssistant Async friendly. """ try: - return hass.data[DATA_KEY][comp_or_platform] # type: ignore + return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore except KeyError: pass - cache = hass.data.get(DATA_KEY) + cache = hass.data.get(DATA_COMPONENTS) if cache is None: if not _async_mount_config_dir(hass): return None - cache = hass.data[DATA_KEY] = {} + cache = hass.data[DATA_COMPONENTS] = {} for path in ('{}.{}'.format(base, comp_or_platform) for base in base_paths): @@ -392,9 +328,18 @@ class Components: def __getattr__(self, comp_name: str) -> ModuleWrapper: """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: raise ImportError('Unable to load {}'.format(comp_name)) + wrapped = ModuleWrapper(self._hass, component) setattr(self, comp_name, wrapped) return wrapped diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 688082d85b7..74e1469f9d1 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -168,12 +168,11 @@ async def _async_setup_component(hass: core.HomeAssistant, if result is not True: log_error("Component {!r} did not return boolean if setup was " "successful. Disabling component.".format(domain)) - loader.set_component(hass, domain, None) return False if hass.config_entries: 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 diff --git a/tests/common.py b/tests/common.py index ce3dc51fcb9..6e3b95725b0 100644 --- a/tests/common.py +++ b/tests/common.py @@ -4,6 +4,7 @@ import functools as ft import json import logging import os +import uuid import sys import threading @@ -607,7 +608,7 @@ class MockConfigEntry(config_entries.ConfigEntry): connection_class=config_entries.CONN_CLASS_UNKNOWN): """Initialize a mock config entry.""" kwargs = { - 'entry_id': entry_id or 'mock-id', + 'entry_id': entry_id or uuid.uuid4().hex, 'domain': domain, 'data': data or {}, 'options': options, @@ -911,7 +912,7 @@ def mock_integration(hass, module): hass.data.setdefault( loader.DATA_INTEGRATIONS, {} )[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): @@ -921,8 +922,8 @@ def mock_entity_platform(hass, platform_path, module): hue.light. """ domain, platform_name = platform_path.split('.') - integration_cache = hass.data.setdefault(loader.DATA_KEY, {}) - module_cache = hass.data.setdefault(loader.DATA_KEY, {}) + integration_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {}) + module_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {}) if platform_name not in integration_cache: mock_integration(hass, MockModule(platform_name)) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6d2304433ab..1b5a40ade8a 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -12,15 +12,15 @@ from homeassistant.config_entries import HANDLERS from homeassistant.core import callback from homeassistant.setup import async_setup_component 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) def mock_test_component(hass): """Ensure a component called 'test' exists.""" - set_component(hass, 'test', MockModule('test')) + mock_integration(hass, MockModule('test')) @pytest.fixture @@ -244,8 +244,8 @@ def test_abort(hass, client): @asyncio.coroutine def test_create_account(hass, client): """Test a flow that creates an account.""" - set_component( - hass, 'test', + mock_integration( + hass, MockModule('test', async_setup_entry=mock_coro_func(True))) class TestFlow(core_ce.ConfigFlow): @@ -283,8 +283,8 @@ def test_create_account(hass, client): @asyncio.coroutine def test_two_step_flow(hass, client): """Test we can finish a two step flow.""" - set_component( - hass, 'test', + mock_integration( + hass, MockModule('test', async_setup_entry=mock_coro_func(True))) 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): """Test we can't finish a two step flow.""" - set_component( - hass, 'test', + mock_integration( + hass, MockModule('test', async_setup_entry=mock_coro_func(True))) 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): """Test we can finish a two step options flow.""" - set_component( - hass, 'test', + mock_integration( + hass, MockModule('test', async_setup_entry=mock_coro_func(True))) class TestFlow(core_ce.ConfigFlow): diff --git a/tests/components/mqtt/test_vacuum.py b/tests/components/mqtt/test_vacuum.py index ba7a930781f..4140177a929 100644 --- a/tests/components/mqtt/test_vacuum.py +++ b/tests/components/mqtt/test_vacuum.py @@ -136,9 +136,10 @@ async def test_all_commands(hass, mock_publish): entity_id='vacuum.mqtttest') await hass.async_block_till_done() await hass.async_block_till_done() - mock_publish.async_publish.assert_called_once_with( - 'vacuum/send_command', '{"command": "44 FE 93", "key": "value"}', - 0, False) + assert json.loads(mock_publish.async_publish.mock_calls[-1][1][1]) == { + "command": "44 FE 93", + "key": "value" + } async def test_status(hass, mock_publish): diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index c76278f9b22..0a07ebceb21 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -7,7 +7,7 @@ from homeassistant import core, loader from homeassistant.components import switch 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 @@ -80,7 +80,7 @@ class TestSwitch(unittest.TestCase): test_platform = loader.get_component(self.hass, 'test.switch') 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) assert setup_component( diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index c198325b350..04c91cfdc08 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -3,9 +3,10 @@ from unittest.mock import patch, Mock 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 tests.common import MockConfigEntry, MockModule, mock_coro +from tests.common import ( + MockConfigEntry, MockModule, mock_coro, mock_integration) @pytest.fixture @@ -101,7 +102,7 @@ async def test_discovery_confirmation(hass, discovery_flow_conf): async def test_multiple_discoveries(hass, discovery_flow_conf): """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( '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): """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 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_unload_entry = Mock(return_value=mock_coro(True)) - loader.set_component(hass, 'test_single', MockModule( + mock_integration(hass, MockModule( 'test_single', async_setup_entry=async_setup_entry, async_unload_entry=async_unload_entry, diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index 0023644a350..3a7f29273e5 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -3,13 +3,14 @@ from unittest.mock import patch import pytest -from homeassistant import loader, setup +from homeassistant import setup from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery 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: @@ -128,17 +129,17 @@ class TestHelpersDiscovery: """Set up mock platform.""" platform_calls.append('disc' if discovery_info else 'component') - loader.set_component( - self.hass, 'test_component', + mock_integration( + self.hass, MockModule('test_component', setup=component_setup)) # dependencies are only set in component level # since we are using manifest to hold them - loader.set_component( - self.hass, 'test_circular', + mock_integration( + self.hass, MockModule('test_circular', dependencies=['test_component'])) - loader.set_component( - self.hass, 'test_circular.switch', + mock_entity_platform( + self.hass, 'switch.test_circular', MockPlatform(setup_platform)) setup.setup_component(self.hass, 'test_component', { @@ -180,12 +181,12 @@ class TestHelpersDiscovery: component_calls.append(1) return True - loader.set_component( - self.hass, 'test_component1', + mock_integration( + self.hass, MockModule('test_component1', setup=component1_setup)) - loader.set_component( - self.hass, 'test_component2', + mock_integration( + self.hass, MockModule('test_component2', setup=component2_setup)) @callback diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 790b7d638e4..cb433a16a7c 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -10,7 +10,6 @@ from datetime import timedelta import pytest import homeassistant.core as ha -import homeassistant.loader as loader from homeassistant.exceptions import PlatformNotReady from homeassistant.components import group 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.""" platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady, None]) - loader.set_component(hass, 'mod1', - MockModule('mod1')) - loader.set_component(hass, 'mod1.test_domain', + mock_integration(hass, MockModule('mod1')) + mock_entity_platform(hass, 'test_domain.mod1', MockPlatform(platform1_setup)) 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 with the same name has already been loaded. """ - loader.set_component(hass, 'test_component', - MockModule('test_component', - dependencies=['test_component2'])) - loader.set_component(hass, 'test_component2', - MockModule('test_component2')) - loader.set_component(hass, 'test_component.test_domain', MockPlatform()) + mock_integration(hass, MockModule('test_component', + dependencies=['test_component2'])) + mock_integration(hass, MockModule('test_component2')) + mock_entity_platform(hass, 'test_domain.test_component', MockPlatform()) component = EntityComponent(_LOGGER, DOMAIN, hass) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index cd3f9fd1a89..a8a1211f4c2 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch 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.exceptions import ConfigEntryNotReady 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_migrate_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_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_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_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_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_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)) mock_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_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()) mock_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_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)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_entry)) 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_remove_entry = MagicMock( side_effect=lambda *args, **kwargs: mock_coro()) - loader.set_component(hass, 'test', MockModule( + mock_integration(hass, MockModule( 'test', async_setup_entry=mock_setup_entry, async_unload_entry=mock_unload_entry, @@ -304,13 +304,12 @@ def test_remove_entry_raises(hass, manager): """Mock unload entry function.""" raise Exception("BROKEN") - loader.set_component( - hass, 'test', - MockModule('comp', async_unload_entry=mock_unload_entry)) + mock_integration(hass, MockModule( + 'comp', async_unload_entry=mock_unload_entry)) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) MockConfigEntry( - domain='test', + domain='comp', entry_id='test2', state=config_entries.ENTRY_STATE_LOADED ).add_to_manager(manager) @@ -330,15 +329,14 @@ def test_remove_entry_raises(hass, manager): @asyncio.coroutine 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)) - loader.set_component( - hass, 'test', - MockModule('comp', async_unload_entry=mock_unload_entry)) + mock_integration(hass, MockModule( + 'comp', async_unload_entry=mock_unload_entry)) 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) 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()] == \ ['test1', 'test3'] - assert len(mock_unload_entry.mock_calls) == 1 + assert len(mock_unload_entry.mock_calls) == 0 @asyncio.coroutine @@ -360,8 +358,8 @@ def test_add_entry_calls_setup_entry(hass, manager): """Test we call setup_config_entry.""" mock_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'comp', + mock_integration( + hass, MockModule('comp', async_setup_entry=mock_setup_entry)) class TestFlow(config_entries.ConfigFlow): @@ -416,9 +414,8 @@ def test_domains_gets_uniques(manager): async def test_saving_and_loading(hass): """Test that we're saving and loading correctly.""" - loader.set_component( - hass, 'test', - MockModule('test', async_setup_entry=lambda *args: mock_coro(True))) + mock_integration(hass, MockModule( + 'test', async_setup_entry=lambda *args: mock_coro(True))) class TestFlow(config_entries.ConfigFlow): VERSION = 5 @@ -480,13 +477,13 @@ async def test_forward_entry_sets_up_component(hass): entry = MockConfigEntry(domain='original') mock_original_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'original', + mock_integration( + hass, MockModule('original', async_setup_entry=mock_original_setup_entry)) mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component( - hass, 'forwarded', + mock_integration( + hass, MockModule('forwarded', async_setup_entry=mock_forwarded_setup_entry)) 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_entry = MagicMock() - hass, loader.set_component(hass, 'forwarded', MockModule( + mock_integration(hass, MockModule( 'forwarded', async_setup=mock_setup, 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): """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', {}) class TestFlow(config_entries.ConfigFlow): @@ -550,7 +547,7 @@ async def test_discovery_notification(hass): async def test_discovery_notification_not_created(hass): """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', {}) class TestFlow(config_entries.ConfigFlow): @@ -630,8 +627,8 @@ async def test_setup_raise_not_ready(hass, caplog): entry = MockConfigEntry(domain='test') mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) - loader.set_component( - hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry)) + mock_integration( + hass, MockModule('test', async_setup_entry=mock_setup_entry)) with patch('homeassistant.helpers.event.async_call_later') as mock_call: await entry.async_setup(hass) @@ -656,8 +653,8 @@ async def test_setup_retrying_during_unload(hass): entry = MockConfigEntry(domain='test') mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) - loader.set_component( - hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry)) + mock_integration( + hass, MockModule('test', async_setup_entry=mock_setup_entry)) with patch('homeassistant.helpers.event.async_call_later') as mock_call: 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_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', async_setup=mock_setup, 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_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', async_setup=mock_setup, 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)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', 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)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', 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)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', 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_unload_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', async_setup=async_setup, 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_unload_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', async_setup=async_setup, 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_unload_entry = MagicMock(return_value=mock_coro(True)) - loader.set_component(hass, 'comp', MockModule( + mock_integration(hass, MockModule( 'comp', async_setup=async_setup, async_setup_entry=async_setup_entry, diff --git a/tests/test_loader.py b/tests/test_loader.py index 12afbfdfb80..9e1707bb7b2 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -8,14 +8,6 @@ from homeassistant.components.hue import light as hue_light 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): """Test if get_component works.""" 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 -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): """Test resolving integration.""" integration = await loader.async_get_integration(hass, 'hue') diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 9f98bef3517..dcc107ea07e 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -2,13 +2,14 @@ import os from unittest.mock import patch, call -from homeassistant import loader, setup +from homeassistant import setup from homeassistant.requirements import ( CONSTRAINT_FILE, PackageLoadable, async_process_requirements) 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( os.path.join(os.path.dirname(__file__), '..', 'resources')) @@ -43,8 +44,8 @@ class TestRequirements: mock_venv.return_value = True mock_dirname.return_value = 'ha_package_path' self.hass.config.skip_pip = False - loader.set_component( - self.hass, 'comp', + mock_integration( + self.hass, MockModule('comp', requirements=['package==0.0.1'])) assert setup.setup_component(self.hass, 'comp', {}) assert 'comp' in self.hass.config.components @@ -60,8 +61,8 @@ class TestRequirements: """Test requirement installed in deps directory.""" mock_dirname.return_value = 'ha_package_path' self.hass.config.skip_pip = False - loader.set_component( - self.hass, 'comp', + mock_integration( + self.hass, MockModule('comp', requirements=['package==0.0.1'])) assert setup.setup_component(self.hass, 'comp', {}) assert 'comp' in self.hass.config.components diff --git a/tests/test_setup.py b/tests/test_setup.py index f3c1a669359..47b47260c9b 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -494,7 +494,6 @@ class TestSetup: MockModule('disabled_component', setup=lambda hass, config: None)) 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 self.hass.data.pop(setup.DATA_SETUP)