2017-04-30 05:04:49 +00:00
|
|
|
"""All methods needed to bootstrap a Home Assistant instance."""
|
2017-03-05 09:41:54 +00:00
|
|
|
import asyncio
|
|
|
|
import logging.handlers
|
2017-06-02 05:44:44 +00:00
|
|
|
from timeit import default_timer as timer
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
from types import ModuleType
|
2018-11-28 21:20:13 +00:00
|
|
|
from typing import Awaitable, Callable, Optional, Dict, List
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2018-01-30 11:30:47 +00:00
|
|
|
from homeassistant import requirements, core, loader, config as conf_util
|
2017-11-03 14:43:30 +00:00
|
|
|
from homeassistant.config import async_notify_setup_error
|
2018-01-30 11:30:47 +00:00
|
|
|
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
|
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2018-03-11 17:01:12 +00:00
|
|
|
from homeassistant.util.async_ import run_coroutine_threadsafe
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2018-01-30 11:30:47 +00:00
|
|
|
|
2017-03-05 09:41:54 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
ATTR_COMPONENT = 'component'
|
|
|
|
|
|
|
|
DATA_SETUP = 'setup_tasks'
|
2018-01-30 11:30:47 +00:00
|
|
|
DATA_DEPS_REQS = 'deps_reqs_processed'
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2017-03-08 04:31:57 +00:00
|
|
|
SLOW_SETUP_WARNING = 10
|
|
|
|
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
def setup_component(hass: core.HomeAssistant, domain: str,
|
2019-04-14 23:59:06 +00:00
|
|
|
config: Dict) -> bool:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up a component and all its dependencies."""
|
2018-07-13 17:14:45 +00:00
|
|
|
return run_coroutine_threadsafe( # type: ignore
|
2019-05-23 04:09:59 +00:00
|
|
|
async_setup_component(hass, domain, config), hass.loop).result()
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
async def async_setup_component(hass: core.HomeAssistant, domain: str,
|
2019-04-14 23:59:06 +00:00
|
|
|
config: Dict) -> bool:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up a component and all its dependencies.
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
|
|
|
if domain in hass.config.components:
|
|
|
|
return True
|
|
|
|
|
2019-04-14 23:59:06 +00:00
|
|
|
setup_tasks = hass.data.setdefault(DATA_SETUP, {})
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2019-04-14 23:59:06 +00:00
|
|
|
if domain in setup_tasks:
|
2018-07-13 17:14:45 +00:00
|
|
|
return await setup_tasks[domain] # type: ignore
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2018-07-13 10:24:51 +00:00
|
|
|
task = setup_tasks[domain] = hass.async_create_task(
|
2017-03-05 09:41:54 +00:00
|
|
|
_async_setup_component(hass, domain, config))
|
|
|
|
|
2018-07-13 17:14:45 +00:00
|
|
|
return await task # type: ignore
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
async def _async_process_dependencies(
|
|
|
|
hass: core.HomeAssistant, config: Dict, name: str,
|
|
|
|
dependencies: List[str]) -> bool:
|
2017-03-05 09:41:54 +00:00
|
|
|
"""Ensure all dependencies are set up."""
|
|
|
|
blacklisted = [dep for dep in dependencies
|
|
|
|
if dep in loader.DEPENDENCY_BLACKLIST]
|
|
|
|
|
2019-02-08 04:07:15 +00:00
|
|
|
if blacklisted and name != 'default_config':
|
2018-08-19 20:29:08 +00:00
|
|
|
_LOGGER.error("Unable to set up dependencies of %s: "
|
2017-04-30 05:04:49 +00:00
|
|
|
"found blacklisted dependencies: %s",
|
2017-03-05 09:41:54 +00:00
|
|
|
name, ', '.join(blacklisted))
|
|
|
|
return False
|
|
|
|
|
|
|
|
tasks = [async_setup_component(hass, dep, config) for dep
|
|
|
|
in dependencies]
|
|
|
|
|
|
|
|
if not tasks:
|
|
|
|
return True
|
|
|
|
|
2019-05-23 04:09:59 +00:00
|
|
|
results = await asyncio.gather(*tasks)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
failed = [dependencies[idx] for idx, res
|
|
|
|
in enumerate(results) if not res]
|
|
|
|
|
|
|
|
if failed:
|
2018-08-19 20:29:08 +00:00
|
|
|
_LOGGER.error("Unable to set up dependencies of %s. "
|
2017-04-30 05:04:49 +00:00
|
|
|
"Setup failed for dependencies: %s",
|
2017-03-05 09:41:54 +00:00
|
|
|
name, ', '.join(failed))
|
|
|
|
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
async def _async_setup_component(hass: core.HomeAssistant,
|
2018-07-23 08:24:39 +00:00
|
|
|
domain: str, config: Dict) -> bool:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up a component for Home Assistant.
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2018-07-23 08:24:39 +00:00
|
|
|
def log_error(msg: str, link: bool = True) -> None:
|
2017-03-05 09:41:54 +00:00
|
|
|
"""Log helper."""
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.error("Setup failed for %s: %s", domain, msg)
|
2017-03-05 09:41:54 +00:00
|
|
|
async_notify_setup_error(hass, domain, link)
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
try:
|
|
|
|
integration = await loader.async_get_integration(hass, domain)
|
|
|
|
except loader.IntegrationNotFound:
|
|
|
|
log_error("Integration not found.", False)
|
|
|
|
return False
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2019-02-07 21:56:40 +00:00
|
|
|
# Validate all dependencies exist and there are no circular dependencies
|
|
|
|
try:
|
2019-04-09 16:30:32 +00:00
|
|
|
await loader.async_component_dependencies(hass, domain)
|
|
|
|
except loader.IntegrationNotFound as err:
|
2019-02-07 21:56:40 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Not setting up %s because we are unable to resolve "
|
|
|
|
"(sub)dependency %s", domain, err.domain)
|
|
|
|
return False
|
|
|
|
except loader.CircularDependency as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Not setting up %s because it contains a circular dependency: "
|
|
|
|
"%s -> %s", domain, err.from_domain, err.to_domain)
|
2017-03-05 09:41:54 +00:00
|
|
|
return False
|
|
|
|
|
2019-04-15 23:45:46 +00:00
|
|
|
# Process requirements as soon as possible, so we can import the component
|
|
|
|
# without requiring imports to be in functions.
|
|
|
|
try:
|
|
|
|
await async_process_deps_reqs(hass, config, integration)
|
|
|
|
except HomeAssistantError as err:
|
|
|
|
log_error(str(err))
|
|
|
|
return False
|
|
|
|
|
2019-04-14 14:23:01 +00:00
|
|
|
processed_config = await conf_util.async_process_component_config(
|
|
|
|
hass, config, integration)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
if processed_config is None:
|
2017-04-30 05:04:49 +00:00
|
|
|
log_error("Invalid config.")
|
2017-03-05 09:41:54 +00:00
|
|
|
return False
|
|
|
|
|
2017-06-02 05:44:44 +00:00
|
|
|
start = timer()
|
2017-03-08 04:31:57 +00:00
|
|
|
_LOGGER.info("Setting up %s", domain)
|
2018-01-02 20:16:32 +00:00
|
|
|
|
2019-04-15 23:45:46 +00:00
|
|
|
try:
|
|
|
|
component = integration.get_component()
|
|
|
|
except ImportError:
|
|
|
|
log_error("Unable to import component", False)
|
|
|
|
return False
|
|
|
|
|
2018-01-02 20:16:32 +00:00
|
|
|
if hasattr(component, 'PLATFORM_SCHEMA'):
|
|
|
|
# Entity components have their own warning
|
|
|
|
warn_task = None
|
|
|
|
else:
|
|
|
|
warn_task = hass.loop.call_later(
|
|
|
|
SLOW_SETUP_WARNING, _LOGGER.warning,
|
|
|
|
"Setup of %s is taking over %s seconds.",
|
|
|
|
domain, SLOW_SETUP_WARNING)
|
2017-03-08 04:31:57 +00:00
|
|
|
|
2017-03-05 09:41:54 +00:00
|
|
|
try:
|
2018-01-30 11:30:47 +00:00
|
|
|
if hasattr(component, 'async_setup'):
|
2018-05-12 21:44:53 +00:00
|
|
|
result = await component.async_setup( # type: ignore
|
|
|
|
hass, processed_config)
|
2019-04-26 19:41:30 +00:00
|
|
|
elif hasattr(component, 'setup'):
|
2018-07-13 10:24:51 +00:00
|
|
|
result = await hass.async_add_executor_job(
|
2018-05-12 21:44:53 +00:00
|
|
|
component.setup, hass, processed_config) # type: ignore
|
2019-04-26 19:41:30 +00:00
|
|
|
else:
|
|
|
|
log_error("No setup function defined.")
|
|
|
|
return False
|
2017-03-05 09:41:54 +00:00
|
|
|
except Exception: # pylint: disable=broad-except
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.exception("Error during setup of component %s", domain)
|
2017-03-05 09:41:54 +00:00
|
|
|
async_notify_setup_error(hass, domain, True)
|
|
|
|
return False
|
2017-03-08 04:31:57 +00:00
|
|
|
finally:
|
2017-06-02 05:44:44 +00:00
|
|
|
end = timer()
|
2018-01-02 20:16:32 +00:00
|
|
|
if warn_task:
|
|
|
|
warn_task.cancel()
|
2017-06-02 05:44:44 +00:00
|
|
|
_LOGGER.info("Setup of domain %s took %.1f seconds.", domain, end - start)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
if result is False:
|
2019-07-05 22:24:26 +00:00
|
|
|
log_error("Integration failed to initialize.")
|
2017-03-05 09:41:54 +00:00
|
|
|
return False
|
2018-07-23 08:16:05 +00:00
|
|
|
if result is not True:
|
2019-07-05 22:24:26 +00:00
|
|
|
log_error("Integration {!r} did not return boolean if setup was "
|
2018-11-06 11:41:39 +00:00
|
|
|
"successful. Disabling component.".format(domain))
|
2017-03-05 09:41:54 +00:00
|
|
|
return False
|
|
|
|
|
2018-07-17 22:28:44 +00:00
|
|
|
if hass.config_entries:
|
|
|
|
for entry in hass.config_entries.async_entries(domain):
|
2019-04-15 02:07:05 +00:00
|
|
|
await entry.async_setup(hass, integration=integration)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-04-26 19:41:30 +00:00
|
|
|
hass.config.components.add(domain)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2017-04-30 05:04:49 +00:00
|
|
|
# Cleanup
|
2017-03-05 09:41:54 +00:00
|
|
|
if domain in hass.data[DATA_SETUP]:
|
|
|
|
hass.data[DATA_SETUP].pop(domain)
|
|
|
|
|
|
|
|
hass.bus.async_fire(
|
2018-05-12 21:44:53 +00:00
|
|
|
EVENT_COMPONENT_LOADED,
|
2019-04-26 19:41:30 +00:00
|
|
|
{ATTR_COMPONENT: domain}
|
2017-03-05 09:41:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
async def async_prepare_setup_platform(hass: core.HomeAssistant,
|
|
|
|
hass_config: Dict,
|
2018-02-25 11:38:46 +00:00
|
|
|
domain: str, platform_name: str) \
|
2017-03-05 09:41:54 +00:00
|
|
|
-> Optional[ModuleType]:
|
|
|
|
"""Load a platform and makes sure dependencies are setup.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2019-01-11 19:30:22 +00:00
|
|
|
platform_path = PLATFORM_FORMAT.format(domain=domain,
|
|
|
|
platform=platform_name)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
def log_error(msg: str) -> None:
|
2017-03-05 09:41:54 +00:00
|
|
|
"""Log helper."""
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.error("Unable to prepare setup for platform %s: %s",
|
2019-05-24 22:54:04 +00:00
|
|
|
platform_path, msg)
|
2017-03-05 09:41:54 +00:00
|
|
|
async_notify_setup_error(hass, platform_path)
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
try:
|
|
|
|
integration = await loader.async_get_integration(hass, platform_name)
|
|
|
|
except loader.IntegrationNotFound:
|
|
|
|
log_error("Integration not found")
|
|
|
|
return None
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2019-04-15 23:45:46 +00:00
|
|
|
# Process deps and reqs as soon as possible, so that requirements are
|
|
|
|
# available when we import the platform.
|
|
|
|
try:
|
|
|
|
await async_process_deps_reqs(hass, hass_config, integration)
|
|
|
|
except HomeAssistantError as err:
|
|
|
|
log_error(str(err))
|
|
|
|
return None
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
try:
|
|
|
|
platform = integration.get_platform(domain)
|
2019-05-24 22:54:04 +00:00
|
|
|
except ImportError as exc:
|
|
|
|
log_error("Platform not found ({}).".format(exc))
|
2017-03-05 09:41:54 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
# Already loaded
|
2018-07-23 08:16:05 +00:00
|
|
|
if platform_path in hass.config.components:
|
2017-03-05 09:41:54 +00:00
|
|
|
return platform
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
# Platforms cannot exist on their own, they are part of their integration.
|
|
|
|
# If the integration is not set up yet, and can be set up, set it up.
|
|
|
|
if integration.domain not in hass.config.components:
|
|
|
|
try:
|
|
|
|
component = integration.get_component()
|
2019-05-24 22:54:04 +00:00
|
|
|
except ImportError as exc:
|
|
|
|
log_error("Unable to import the component ({}).".format(exc))
|
2019-04-11 08:26:36 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
if (hasattr(component, 'setup')
|
|
|
|
or hasattr(component, 'async_setup')):
|
|
|
|
if not await async_setup_component(
|
|
|
|
hass, integration.domain, hass_config
|
|
|
|
):
|
|
|
|
log_error("Unable to set up component.")
|
|
|
|
return None
|
|
|
|
|
2018-01-30 11:30:47 +00:00
|
|
|
return platform
|
|
|
|
|
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
async def async_process_deps_reqs(
|
2019-04-11 08:26:36 +00:00
|
|
|
hass: core.HomeAssistant, config: Dict,
|
|
|
|
integration: loader.Integration) -> None:
|
2018-01-30 11:30:47 +00:00
|
|
|
"""Process all dependencies and requirements for a module.
|
|
|
|
|
|
|
|
Module is a Python module of either a component or platform.
|
|
|
|
"""
|
|
|
|
processed = hass.data.get(DATA_DEPS_REQS)
|
|
|
|
|
|
|
|
if processed is None:
|
|
|
|
processed = hass.data[DATA_DEPS_REQS] = set()
|
2019-04-11 08:26:36 +00:00
|
|
|
elif integration.domain in processed:
|
2018-01-30 11:30:47 +00:00
|
|
|
return
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
if integration.dependencies and not await _async_process_dependencies(
|
|
|
|
hass,
|
|
|
|
config,
|
|
|
|
integration.domain,
|
|
|
|
integration.dependencies
|
|
|
|
):
|
|
|
|
raise HomeAssistantError("Could not set up all dependencies.")
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
if (not hass.config.skip_pip and integration.requirements and
|
|
|
|
not await requirements.async_process_requirements(
|
|
|
|
hass, integration.domain, integration.requirements)):
|
|
|
|
raise HomeAssistantError("Could not install all requirements.")
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
processed.add(integration.domain)
|
2018-11-28 21:20:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
@core.callback
|
|
|
|
def async_when_setup(
|
|
|
|
hass: core.HomeAssistant, component: str,
|
|
|
|
when_setup_cb: Callable[
|
|
|
|
[core.HomeAssistant, str], Awaitable[None]]) -> None:
|
|
|
|
"""Call a method when a component is setup."""
|
|
|
|
async def when_setup() -> None:
|
|
|
|
"""Call the callback."""
|
|
|
|
try:
|
|
|
|
await when_setup_cb(hass, component)
|
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
_LOGGER.exception('Error handling when_setup callback for %s',
|
|
|
|
component)
|
|
|
|
|
|
|
|
# Running it in a new task so that it always runs after
|
|
|
|
if component in hass.config.components:
|
|
|
|
hass.async_create_task(when_setup())
|
|
|
|
return
|
|
|
|
|
|
|
|
unsub = None
|
|
|
|
|
|
|
|
async def loaded_event(event: core.Event) -> None:
|
|
|
|
"""Call the callback."""
|
|
|
|
if event.data[ATTR_COMPONENT] != component:
|
|
|
|
return
|
|
|
|
|
|
|
|
unsub() # type: ignore
|
|
|
|
await when_setup()
|
|
|
|
|
|
|
|
unsub = hass.bus.async_listen(EVENT_COMPONENT_LOADED, loaded_event)
|