2017-04-30 05:04:49 +00:00
|
|
|
"""All methods needed to bootstrap a Home Assistant instance."""
|
2021-03-17 16:34:55 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2017-03-05 09:41:54 +00:00
|
|
|
import asyncio
|
2021-09-29 14:32:11 +00:00
|
|
|
from collections.abc import Awaitable, Callable, Generator, Iterable
|
2021-04-05 08:11:44 +00:00
|
|
|
import contextlib
|
2022-02-14 13:24:58 +00:00
|
|
|
from datetime import timedelta
|
2017-03-05 09:41:54 +00:00
|
|
|
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
|
2021-12-27 16:55:17 +00:00
|
|
|
from typing import Any
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2021-12-23 19:14:47 +00:00
|
|
|
from . import config as conf_util, core, loader, requirements
|
|
|
|
from .config import async_notify_setup_error
|
|
|
|
from .const import (
|
2021-04-13 21:10:58 +00:00
|
|
|
EVENT_COMPONENT_LOADED,
|
|
|
|
EVENT_HOMEASSISTANT_START,
|
|
|
|
PLATFORM_FORMAT,
|
2021-12-03 08:31:17 +00:00
|
|
|
Platform,
|
2021-04-13 21:10:58 +00:00
|
|
|
)
|
2021-12-23 19:14:47 +00:00
|
|
|
from .core import CALLBACK_TYPE
|
2022-02-02 14:06:27 +00:00
|
|
|
from .exceptions import DependencyError, HomeAssistantError
|
2021-12-23 19:14:47 +00:00
|
|
|
from .helpers.typing import ConfigType
|
|
|
|
from .util import dt as dt_util, ensure_unique_string
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_COMPONENT = "component"
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2021-12-03 08:31:17 +00:00
|
|
|
BASE_PLATFORMS = {platform.value for platform in Platform}
|
2021-03-29 10:51:48 +00:00
|
|
|
|
2022-11-17 20:52:57 +00:00
|
|
|
# DATA_SETUP is a dict[str, asyncio.Task[bool]], indicating domains which are currently
|
2023-01-15 22:00:51 +00:00
|
|
|
# being setup or which failed to setup:
|
|
|
|
# - Tasks are added to DATA_SETUP by `async_setup_component`, the key is the domain
|
|
|
|
# being setup and the Task is the `_async_setup_component` helper.
|
|
|
|
# - Tasks are removed from DATA_SETUP if setup was successful, that is,
|
|
|
|
# the task returned True.
|
2022-11-17 20:52:57 +00:00
|
|
|
DATA_SETUP = "setup_tasks"
|
|
|
|
|
2023-01-15 22:00:51 +00:00
|
|
|
# DATA_SETUP_DONE is a dict [str, asyncio.Event], indicating components which
|
|
|
|
# will be setup:
|
|
|
|
# - Events are added to DATA_SETUP_DONE during bootstrap by
|
|
|
|
# async_set_domains_to_be_loaded, the key is the domain which will be loaded.
|
|
|
|
# - Events are set and removed from DATA_SETUP_DONE when async_setup_component
|
|
|
|
# is finished, regardless of if the setup was successful or not.
|
2020-06-20 00:24:33 +00:00
|
|
|
DATA_SETUP_DONE = "setup_done"
|
2022-11-17 20:52:57 +00:00
|
|
|
|
2023-01-15 22:00:51 +00:00
|
|
|
# DATA_SETUP_DONE is a dict [str, datetime], indicating when an attempt
|
|
|
|
# to setup a component started.
|
2020-05-28 23:48:42 +00:00
|
|
|
DATA_SETUP_STARTED = "setup_started"
|
2022-11-17 20:52:57 +00:00
|
|
|
|
2023-01-15 22:00:51 +00:00
|
|
|
# DATA_SETUP_TIME is a dict [str, timedelta], indicating how time was spent
|
|
|
|
# setting up a component.
|
2021-04-05 08:11:44 +00:00
|
|
|
DATA_SETUP_TIME = "setup_time"
|
|
|
|
|
2019-07-31 19:25:30 +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
|
2020-08-05 12:58:19 +00:00
|
|
|
SLOW_SETUP_MAX_WAIT = 300
|
2017-03-08 04:31:57 +00:00
|
|
|
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2020-06-20 00:24:33 +00:00
|
|
|
@core.callback
|
2021-03-17 16:34:55 +00:00
|
|
|
def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: set[str]) -> None:
|
2020-06-20 00:24:33 +00:00
|
|
|
"""Set domains that are going to be loaded from the config.
|
|
|
|
|
2022-11-17 20:52:57 +00:00
|
|
|
This allow us to:
|
|
|
|
- Properly handle after_dependencies.
|
|
|
|
- Keep track of domains which will load but have not yet finished loading
|
2020-06-20 00:24:33 +00:00
|
|
|
"""
|
|
|
|
hass.data[DATA_SETUP_DONE] = {domain: asyncio.Event() for domain in domains}
|
|
|
|
|
|
|
|
|
2020-03-14 10:39:28 +00:00
|
|
|
def setup_component(hass: core.HomeAssistant, domain: str, config: ConfigType) -> bool:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up a component and all its dependencies."""
|
2019-10-01 14:59:06 +00:00
|
|
|
return asyncio.run_coroutine_threadsafe(
|
2019-07-31 19:25:30 +00:00
|
|
|
async_setup_component(hass, domain, config), hass.loop
|
|
|
|
).result()
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_setup_component(
|
2020-03-14 10:39:28 +00:00
|
|
|
hass: core.HomeAssistant, domain: str, config: ConfigType
|
2019-07-31 19:25:30 +00:00
|
|
|
) -> 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
|
|
|
|
|
2022-02-18 17:15:57 +00:00
|
|
|
setup_tasks: dict[str, asyncio.Task[bool]] = 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:
|
2022-02-18 17:15:57 +00:00
|
|
|
return await setup_tasks[domain]
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2018-07-13 10:24:51 +00:00
|
|
|
task = setup_tasks[domain] = hass.async_create_task(
|
2023-03-05 11:46:02 +00:00
|
|
|
_async_setup_component(hass, domain, config), f"setup component {domain}"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2020-06-20 00:24:33 +00:00
|
|
|
try:
|
2022-01-07 15:48:34 +00:00
|
|
|
return await task
|
2020-06-20 00:24:33 +00:00
|
|
|
finally:
|
|
|
|
if domain in hass.data.get(DATA_SETUP_DONE, {}):
|
|
|
|
hass.data[DATA_SETUP_DONE].pop(domain).set()
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
async def _async_process_dependencies(
|
2020-06-20 00:24:33 +00:00
|
|
|
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
|
2022-02-02 14:06:27 +00:00
|
|
|
) -> list[str]:
|
|
|
|
"""Ensure all dependencies are set up.
|
|
|
|
|
|
|
|
Returns a list of dependencies which failed to set up.
|
|
|
|
"""
|
2020-08-31 11:54:15 +00:00
|
|
|
dependencies_tasks = {
|
2020-06-20 00:24:33 +00:00
|
|
|
dep: hass.loop.create_task(async_setup_component(hass, dep, config))
|
|
|
|
for dep in integration.dependencies
|
2020-08-31 11:54:15 +00:00
|
|
|
if dep not in hass.config.components
|
2020-06-20 00:24:33 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:02:23 +00:00
|
|
|
after_dependencies_tasks = {}
|
2020-06-20 00:24:33 +00:00
|
|
|
to_be_loaded = hass.data.get(DATA_SETUP_DONE, {})
|
|
|
|
for dep in integration.after_dependencies:
|
2020-08-31 11:54:15 +00:00
|
|
|
if (
|
|
|
|
dep not in dependencies_tasks
|
|
|
|
and dep in to_be_loaded
|
|
|
|
and dep not in hass.config.components
|
|
|
|
):
|
|
|
|
after_dependencies_tasks[dep] = hass.loop.create_task(
|
|
|
|
to_be_loaded[dep].wait()
|
|
|
|
)
|
|
|
|
|
|
|
|
if not dependencies_tasks and not after_dependencies_tasks:
|
2022-02-02 14:06:27 +00:00
|
|
|
return []
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2020-08-31 11:54:15 +00:00
|
|
|
if dependencies_tasks:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Dependency %s will wait for dependencies %s",
|
|
|
|
integration.domain,
|
|
|
|
list(dependencies_tasks),
|
|
|
|
)
|
|
|
|
if after_dependencies_tasks:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Dependency %s will wait for after dependencies %s",
|
|
|
|
integration.domain,
|
|
|
|
list(after_dependencies_tasks),
|
|
|
|
)
|
|
|
|
|
2020-08-05 12:58:19 +00:00
|
|
|
async with hass.timeout.async_freeze(integration.domain):
|
2020-08-31 11:54:15 +00:00
|
|
|
results = await asyncio.gather(
|
|
|
|
*dependencies_tasks.values(), *after_dependencies_tasks.values()
|
|
|
|
)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2020-06-20 00:24:33 +00:00
|
|
|
failed = [
|
2020-08-31 11:54:15 +00:00
|
|
|
domain for idx, domain in enumerate(dependencies_tasks) if not results[idx]
|
2020-06-20 00:24:33 +00:00
|
|
|
]
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
if failed:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
2020-01-02 19:17:10 +00:00
|
|
|
"Unable to set up dependencies of %s. Setup failed for dependencies: %s",
|
2020-06-20 00:24:33 +00:00
|
|
|
integration.domain,
|
2019-07-31 19:25:30 +00:00
|
|
|
", ".join(failed),
|
|
|
|
)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2022-02-02 14:06:27 +00:00
|
|
|
return failed
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def _async_setup_component(
|
2020-03-14 10:39:28 +00:00
|
|
|
hass: core.HomeAssistant, domain: str, config: ConfigType
|
2019-07-31 19:25:30 +00:00
|
|
|
) -> 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.
|
|
|
|
"""
|
2022-03-03 23:03:03 +00:00
|
|
|
integration: loader.Integration | None = None
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2022-03-03 23:03:03 +00:00
|
|
|
def log_error(msg: str) -> None:
|
2017-03-05 09:41:54 +00:00
|
|
|
"""Log helper."""
|
2022-03-03 23:03:03 +00:00
|
|
|
if integration is None:
|
|
|
|
custom = ""
|
|
|
|
link = None
|
|
|
|
else:
|
|
|
|
custom = "" if integration.is_built_in else "custom integration "
|
|
|
|
link = integration.documentation
|
|
|
|
_LOGGER.error("Setup failed for %s%s: %s", custom, 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:
|
2019-12-16 19:16:23 +00:00
|
|
|
log_error("Integration not found.")
|
2019-04-11 08:26:36 +00:00
|
|
|
return False
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2020-08-26 08:20:14 +00:00
|
|
|
if integration.disabled:
|
2021-03-29 20:53:47 +00:00
|
|
|
log_error(f"Dependency is disabled - {integration.disabled}")
|
2020-08-26 08:20:14 +00:00
|
|
|
return False
|
|
|
|
|
2019-02-07 21:56:40 +00:00
|
|
|
# Validate all dependencies exist and there are no circular dependencies
|
2020-06-20 00:24:33 +00:00
|
|
|
if not await integration.resolve_dependencies():
|
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:
|
2022-03-03 23:03:03 +00:00
|
|
|
log_error(str(err))
|
2019-04-15 23:45:46 +00:00
|
|
|
return False
|
|
|
|
|
2019-10-31 18:38:06 +00:00
|
|
|
# Some integrations fail on import because they call functions incorrectly.
|
|
|
|
# So we do it before validating config to catch these errors.
|
|
|
|
try:
|
|
|
|
component = integration.get_component()
|
2020-01-20 06:05:10 +00:00
|
|
|
except ImportError as err:
|
2022-03-03 23:03:03 +00:00
|
|
|
log_error(f"Unable to import component: {err}")
|
2019-10-31 18:38:06 +00:00
|
|
|
return False
|
|
|
|
|
2019-04-14 14:23:01 +00:00
|
|
|
processed_config = await conf_util.async_process_component_config(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass, config, integration
|
|
|
|
)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
if processed_config is None:
|
2022-03-03 23:03:03 +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)
|
2021-04-05 08:11:44 +00:00
|
|
|
with async_start_setup(hass, [domain]):
|
|
|
|
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
|
|
|
|
2021-04-05 08:11:44 +00:00
|
|
|
task = None
|
2021-12-27 16:55:17 +00:00
|
|
|
result: Any | bool = True
|
2021-04-05 08:11:44 +00:00
|
|
|
try:
|
|
|
|
if hasattr(component, "async_setup"):
|
2021-12-27 16:55:17 +00:00
|
|
|
task = component.async_setup(hass, processed_config)
|
2021-04-05 08:11:44 +00:00
|
|
|
elif hasattr(component, "setup"):
|
|
|
|
# This should not be replaced with hass.async_add_executor_job because
|
|
|
|
# we don't want to track this task in case it blocks startup.
|
|
|
|
task = hass.loop.run_in_executor(
|
2021-12-27 16:55:17 +00:00
|
|
|
None, component.setup, hass, processed_config
|
2021-04-05 08:11:44 +00:00
|
|
|
)
|
|
|
|
elif not hasattr(component, "async_setup_entry"):
|
|
|
|
log_error("No setup or config entry setup function defined.")
|
|
|
|
return False
|
|
|
|
|
|
|
|
if task:
|
|
|
|
async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, domain):
|
|
|
|
result = await task
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
_LOGGER.error(
|
2022-12-22 09:12:50 +00:00
|
|
|
(
|
|
|
|
"Setup of %s is taking longer than %s seconds."
|
|
|
|
" Startup will proceed without waiting any longer"
|
|
|
|
),
|
2021-04-05 08:11:44 +00:00
|
|
|
domain,
|
|
|
|
SLOW_SETUP_MAX_WAIT,
|
|
|
|
)
|
|
|
|
return False
|
2023-02-27 14:29:14 +00:00
|
|
|
# pylint: disable-next=broad-except
|
|
|
|
except (asyncio.CancelledError, SystemExit, Exception):
|
2021-04-05 08:11:44 +00:00
|
|
|
_LOGGER.exception("Error during setup of component %s", domain)
|
|
|
|
async_notify_setup_error(hass, domain, integration.documentation)
|
|
|
|
return False
|
|
|
|
finally:
|
|
|
|
end = timer()
|
|
|
|
if warn_task:
|
|
|
|
warn_task.cancel()
|
|
|
|
_LOGGER.info("Setup of domain %s took %.1f seconds", domain, end - start)
|
|
|
|
|
|
|
|
if result is False:
|
|
|
|
log_error("Integration failed to initialize.")
|
|
|
|
return False
|
|
|
|
if result is not True:
|
|
|
|
log_error(
|
|
|
|
f"Integration {domain!r} did not return boolean if setup was "
|
|
|
|
"successful. Disabling component."
|
2019-07-31 20:08:31 +00:00
|
|
|
)
|
2019-04-26 19:41:30 +00:00
|
|
|
return False
|
2020-05-27 18:43:05 +00:00
|
|
|
|
2021-04-05 08:11:44 +00:00
|
|
|
# Flush out async_setup calling create_task. Fragile but covered by test.
|
|
|
|
await asyncio.sleep(0)
|
2023-01-18 09:44:18 +00:00
|
|
|
await hass.config_entries.flow.async_wait_import_flow_initialized(domain)
|
2020-04-15 01:46:41 +00:00
|
|
|
|
2022-11-17 20:52:57 +00:00
|
|
|
# Add to components before the entry.async_setup
|
2022-07-09 15:27:42 +00:00
|
|
|
# call to avoid a deadlock when forwarding platforms
|
|
|
|
hass.config.components.add(domain)
|
|
|
|
|
2021-04-05 08:11:44 +00:00
|
|
|
await asyncio.gather(
|
2021-07-19 08:46:09 +00:00
|
|
|
*(
|
2021-04-05 08:11:44 +00:00
|
|
|
entry.async_setup(hass, integration=integration)
|
|
|
|
for entry in hass.config_entries.async_entries(domain)
|
2021-07-19 08:46:09 +00:00
|
|
|
)
|
2021-04-05 08:11:44 +00:00
|
|
|
)
|
2018-02-16 22:07:38 +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)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: domain})
|
2017-03-05 09:41:54 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_prepare_setup_platform(
|
2020-03-14 10:39:28 +00:00
|
|
|
hass: core.HomeAssistant, hass_config: ConfigType, domain: str, platform_name: str
|
2021-03-17 16:34:55 +00:00
|
|
|
) -> ModuleType | None:
|
2017-03-05 09:41:54 +00:00
|
|
|
"""Load a platform and makes sure dependencies are setup.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2019-07-31 19:25:30 +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."""
|
2022-03-03 23:03:03 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("Unable to prepare setup for platform %s: %s", 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:
|
2019-08-23 16:53:33 +00:00
|
|
|
log_error(f"Platform not found ({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:
|
2019-08-23 16:53:33 +00:00
|
|
|
log_error(f"Unable to import the component ({exc}).")
|
2019-04-11 08:26:36 +00:00
|
|
|
return None
|
|
|
|
|
2021-03-27 08:23:32 +00:00
|
|
|
if (
|
|
|
|
hasattr(component, "setup") or hasattr(component, "async_setup")
|
|
|
|
) and not await async_setup_component(hass, integration.domain, hass_config):
|
|
|
|
log_error("Unable to set up component.")
|
|
|
|
return None
|
2019-04-11 08:26:36 +00:00
|
|
|
|
2018-01-30 11:30:47 +00:00
|
|
|
return platform
|
|
|
|
|
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
async def async_process_deps_reqs(
|
2020-03-14 10:39:28 +00:00
|
|
|
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
|
2019-07-31 19:25:30 +00:00
|
|
|
) -> 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.
|
|
|
|
"""
|
2021-09-18 23:31:35 +00:00
|
|
|
if (processed := hass.data.get(DATA_DEPS_REQS)) is None:
|
2018-01-30 11:30:47 +00:00
|
|
|
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
|
|
|
|
|
2022-02-02 14:06:27 +00:00
|
|
|
if failed_deps := await _async_process_dependencies(hass, config, integration):
|
|
|
|
raise DependencyError(failed_deps)
|
2017-03-05 09:41:54 +00:00
|
|
|
|
2022-09-26 13:59:01 +00:00
|
|
|
async with hass.timeout.async_freeze(integration.domain):
|
|
|
|
await requirements.async_get_integration_with_requirements(
|
|
|
|
hass, integration.domain
|
|
|
|
)
|
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(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass: core.HomeAssistant,
|
|
|
|
component: str,
|
|
|
|
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
|
|
|
|
) -> None:
|
2018-11-28 21:20:13 +00:00
|
|
|
"""Call a method when a component is setup."""
|
2021-04-13 21:10:58 +00:00
|
|
|
_async_when_setup(hass, component, when_setup_cb, False)
|
|
|
|
|
|
|
|
|
|
|
|
@core.callback
|
|
|
|
def async_when_setup_or_start(
|
|
|
|
hass: core.HomeAssistant,
|
|
|
|
component: str,
|
|
|
|
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
|
|
|
|
) -> None:
|
|
|
|
"""Call a method when a component is setup or state is fired."""
|
|
|
|
_async_when_setup(hass, component, when_setup_cb, True)
|
|
|
|
|
|
|
|
|
|
|
|
@core.callback
|
|
|
|
def _async_when_setup(
|
|
|
|
hass: core.HomeAssistant,
|
|
|
|
component: str,
|
|
|
|
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
|
|
|
|
start_event: bool,
|
|
|
|
) -> None:
|
|
|
|
"""Call a method when a component is setup or the start event fires."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-11-28 21:20:13 +00:00
|
|
|
async def when_setup() -> None:
|
|
|
|
"""Call the callback."""
|
|
|
|
try:
|
|
|
|
await when_setup_cb(hass, component)
|
|
|
|
except Exception: # pylint: disable=broad-except
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.exception("Error handling when_setup callback for %s", component)
|
2018-11-28 21:20:13 +00:00
|
|
|
|
|
|
|
if component in hass.config.components:
|
2023-03-05 11:46:02 +00:00
|
|
|
hass.async_create_task(when_setup(), f"when setup {component}")
|
2018-11-28 21:20:13 +00:00
|
|
|
return
|
|
|
|
|
2021-08-16 21:12:06 +00:00
|
|
|
listeners: list[CALLBACK_TYPE] = []
|
2018-11-28 21:20:13 +00:00
|
|
|
|
2021-04-13 21:10:58 +00:00
|
|
|
async def _matched_event(event: core.Event) -> None:
|
|
|
|
"""Call the callback when we matched an event."""
|
|
|
|
for listener in listeners:
|
|
|
|
listener()
|
2018-11-28 21:20:13 +00:00
|
|
|
await when_setup()
|
|
|
|
|
2021-04-13 21:10:58 +00:00
|
|
|
async def _loaded_event(event: core.Event) -> None:
|
|
|
|
"""Call the callback if we loaded the expected component."""
|
|
|
|
if event.data[ATTR_COMPONENT] == component:
|
|
|
|
await _matched_event(event)
|
|
|
|
|
|
|
|
listeners.append(hass.bus.async_listen(EVENT_COMPONENT_LOADED, _loaded_event))
|
|
|
|
if start_event:
|
|
|
|
listeners.append(
|
|
|
|
hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _matched_event)
|
|
|
|
)
|
2021-03-29 10:51:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
@core.callback
|
2021-08-16 21:12:06 +00:00
|
|
|
def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]:
|
2021-03-29 10:51:48 +00:00
|
|
|
"""Return the complete list of loaded integrations."""
|
|
|
|
integrations = set()
|
|
|
|
for component in hass.config.components:
|
|
|
|
if "." not in component:
|
|
|
|
integrations.add(component)
|
|
|
|
continue
|
2022-11-15 20:45:48 +00:00
|
|
|
domain, _, platform = component.partition(".")
|
2021-03-29 10:51:48 +00:00
|
|
|
if domain in BASE_PLATFORMS:
|
|
|
|
integrations.add(platform)
|
|
|
|
return integrations
|
2021-04-05 08:11:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2021-08-16 21:12:06 +00:00
|
|
|
def async_start_setup(
|
|
|
|
hass: core.HomeAssistant, components: Iterable[str]
|
|
|
|
) -> Generator[None, None, None]:
|
2021-04-05 08:11:44 +00:00
|
|
|
"""Keep track of when setup starts and finishes."""
|
|
|
|
setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {})
|
|
|
|
started = dt_util.utcnow()
|
2022-02-14 13:24:58 +00:00
|
|
|
unique_components: dict[str, str] = {}
|
2021-04-05 08:11:44 +00:00
|
|
|
for domain in components:
|
|
|
|
unique = ensure_unique_string(domain, setup_started)
|
|
|
|
unique_components[unique] = domain
|
|
|
|
setup_started[unique] = started
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
2022-02-14 13:24:58 +00:00
|
|
|
setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {})
|
2021-04-05 08:11:44 +00:00
|
|
|
time_taken = dt_util.utcnow() - started
|
|
|
|
for unique, domain in unique_components.items():
|
|
|
|
del setup_started[unique]
|
2022-11-15 20:45:48 +00:00
|
|
|
integration = domain.rpartition(".")[-1]
|
2021-04-05 08:11:44 +00:00
|
|
|
if integration in setup_time:
|
|
|
|
setup_time[integration] += time_taken
|
|
|
|
else:
|
|
|
|
setup_time[integration] = time_taken
|