1 """All methods needed to bootstrap a Home Assistant instance."""
3 from __future__
import annotations
6 from collections
import defaultdict
7 from collections.abc
import Awaitable, Callable, Generator, Mapping
10 from enum
import StrEnum
11 from functools
import partial
14 from types
import ModuleType
15 from typing
import Any, Final, TypedDict
17 from .
import config
as conf_util, core, loader, requirements
20 EVENT_COMPONENT_LOADED,
21 EVENT_HOMEASSISTANT_START,
26 DOMAIN
as HOMEASSISTANT_DOMAIN,
31 from .exceptions
import DependencyError, HomeAssistantError
32 from .helpers
import issue_registry
as ir, singleton, translation
33 from .helpers.issue_registry
import IssueSeverity, async_create_issue
34 from .helpers.typing
import ConfigType
35 from .util.async_
import create_eager_task
36 from .util.hass_dict
import HassKey
38 current_setup_group: contextvars.ContextVar[tuple[str, str |
None] |
None] = (
39 contextvars.ContextVar(
"current_setup_group", default=
None)
43 _LOGGER = logging.getLogger(__name__)
45 ATTR_COMPONENT: Final =
"component"
54 DATA_SETUP: HassKey[dict[str, asyncio.Future[bool]]] =
HassKey(
"setup_tasks")
61 DATA_SETUP_DONE: HassKey[dict[str, asyncio.Future[bool]]] =
HassKey(
"setup_done")
65 DATA_SETUP_STARTED: HassKey[dict[tuple[str, str |
None], float]] =
HassKey(
71 DATA_SETUP_TIME: HassKey[
72 defaultdict[str, defaultdict[str |
None, defaultdict[SetupPhases, float]]]
75 DATA_DEPS_REQS: HassKey[set[str]] =
HassKey(
"deps_reqs_processed")
77 DATA_PERSISTENT_ERRORS: HassKey[dict[str, str |
None]] =
HassKey(
78 "bootstrap_persistent_errors"
81 NOTIFY_FOR_TRANSLATION_KEYS = [
82 "config_validation_err",
83 "platform_config_validation_err",
86 SLOW_SETUP_WARNING = 10
87 SLOW_SETUP_MAX_WAIT = 300
91 """EventComponentLoaded data."""
98 hass: HomeAssistant, component: str, display_link: str |
None =
None
100 """Print a persistent notification.
102 This method must be run in the event loop.
105 from .components
import persistent_notification
107 if (errors := hass.data.get(DATA_PERSISTENT_ERRORS))
is None:
108 errors = hass.data[DATA_PERSISTENT_ERRORS] = {}
110 errors[component] = errors.get(component)
or display_link
112 message =
"The following integrations and platforms could not be set up:\n\n"
114 for name, link
in errors.items():
115 show_logs = f
"[Show logs](/config/logs?filter={name})"
116 part = f
"[{name}]({link})" if link
else name
117 message += f
" - {part} ({show_logs})\n"
119 message +=
"\nPlease check your config and [logs](/config/logs)."
121 persistent_notification.async_create(
122 hass, message,
"Invalid config",
"invalid_config"
128 """Set domains that are going to be loaded from the config.
131 - Properly handle after_dependencies.
132 - Keep track of domains which will load but have not yet finished loading
134 setup_done_futures = hass.data.setdefault(DATA_SETUP_DONE, {})
135 setup_done_futures.update({domain: hass.loop.create_future()
for domain
in domains})
138 def setup_component(hass: core.HomeAssistant, domain: str, config: ConfigType) -> bool:
139 """Set up a component and all its dependencies."""
140 return asyncio.run_coroutine_threadsafe(
146 hass: core.HomeAssistant, domain: str, config: ConfigType
148 """Set up a component and all its dependencies.
150 This method is a coroutine.
152 if domain
in hass.config.components:
155 setup_futures = hass.data.setdefault(DATA_SETUP, {})
156 setup_done_futures = hass.data.setdefault(DATA_SETUP_DONE, {})
158 if existing_setup_future := setup_futures.get(domain):
159 return await existing_setup_future
161 setup_future = hass.loop.create_future()
162 setup_futures[domain] = setup_future
166 setup_future.set_result(result)
167 if setup_done_future := setup_done_futures.pop(domain,
None):
168 setup_done_future.set_result(result)
169 except BaseException
as err:
170 futures = [setup_future]
171 if setup_done_future := setup_done_futures.pop(domain,
None):
172 futures.append(setup_done_future)
173 for future
in futures:
180 future.set_exception(err)
181 with contextlib.suppress(BaseException):
193 """Ensure all dependencies are set up.
195 Returns a list of dependencies which failed to set up.
197 setup_futures = hass.data.setdefault(DATA_SETUP, {})
199 dependencies_tasks = {
200 dep: setup_futures.get(dep)
201 or create_eager_task(
203 name=f
"setup {dep} as dependency of {integration.domain}",
206 for dep
in integration.dependencies
207 if dep
not in hass.config.components
210 after_dependencies_tasks: dict[str, asyncio.Future[bool]] = {}
211 to_be_loaded = hass.data.get(DATA_SETUP_DONE, {})
212 for dep
in integration.after_dependencies:
214 dep
not in dependencies_tasks
215 and dep
in to_be_loaded
216 and dep
not in hass.config.components
218 after_dependencies_tasks[dep] = to_be_loaded[dep]
220 if not dependencies_tasks
and not after_dependencies_tasks:
223 if dependencies_tasks:
225 "Dependency %s will wait for dependencies %s",
227 dependencies_tasks.keys(),
229 if after_dependencies_tasks:
231 "Dependency %s will wait for after dependencies %s",
233 after_dependencies_tasks.keys(),
236 async
with hass.timeout.async_freeze(integration.domain):
237 results = await asyncio.gather(
238 *dependencies_tasks.values(), *after_dependencies_tasks.values()
242 domain
for idx, domain
in enumerate(dependencies_tasks)
if not results[idx]
247 "Unable to set up dependencies of '%s'. Setup failed for dependencies: %s",
260 exc_info: Exception |
None =
None,
263 if integration
is None:
267 custom =
"" if integration.is_built_in
else "custom integration "
268 link = integration.documentation
269 _LOGGER.error(
"Setup failed for %s'%s': %s", custom, domain, msg, exc_info=exc_info)
274 hass: core.HomeAssistant, domain: str, config: ConfigType
276 """Set up a component for Home Assistant.
278 This method is a coroutine.
281 integration = await loader.async_get_integration(hass, domain)
284 if not hass.config.safe_mode
and hass.config_entries.async_entries(domain):
285 ir.async_create_issue(
287 HOMEASSISTANT_DOMAIN,
288 f
"integration_not_found.{domain}",
290 issue_domain=HOMEASSISTANT_DOMAIN,
291 severity=IssueSeverity.ERROR,
292 translation_key=
"integration_not_found",
293 translation_placeholders={
296 data={
"domain": domain},
300 log_error = partial(_log_error_setup_error, hass, domain, integration)
302 if integration.disabled:
303 log_error(f
"Dependency is disabled - {integration.disabled}")
306 integration_set = {domain}
308 load_translations_task: asyncio.Task[
None] |
None =
None
309 if integration.has_translations
and not translation.async_translations_loaded(
310 hass, integration_set
316 load_translations_task = create_eager_task(
317 translation.async_load_integrations(hass, integration_set), loop=hass.loop
320 if not await integration.resolve_dependencies():
327 except HomeAssistantError
as err:
334 component = await integration.async_get_component()
335 except ImportError
as err:
336 log_error(f
"Unable to import component: {err}", err)
339 integration_config_info = await conf_util.async_process_component_config(
340 hass, config, integration, component
342 conf_util.async_handle_component_errors(hass, integration_config_info, integration)
343 processed_config = conf_util.async_drop_config_annotations(
344 integration_config_info, integration
346 for platform_exception
in integration_config_info.exception_info_list:
347 if platform_exception.translation_key
not in NOTIFY_FOR_TRANSLATION_KEYS:
350 hass, platform_exception.platform_path, platform_exception.integration_link
352 if processed_config
is None:
358 domain
in processed_config
359 and not hasattr(component,
"async_setup")
360 and not hasattr(component,
"setup")
361 and not hasattr(component,
"CONFIG_SCHEMA")
365 "The '%s' integration does not support YAML setup, please remove it "
366 "from your configuration"
372 HOMEASSISTANT_DOMAIN,
373 f
"config_entry_only_{domain}",
375 severity=IssueSeverity.ERROR,
377 translation_key=
"config_entry_only",
378 translation_placeholders={
380 "add_integration": f
"/config/integrations/dashboard/add?domain={domain}",
384 _LOGGER.info(
"Setting up %s", domain)
387 if hasattr(component,
"PLATFORM_SCHEMA"):
391 warn_task = hass.loop.call_later(
394 "Setup of %s is taking over %s seconds.",
399 task: Awaitable[bool] |
None =
None
400 result: Any | bool =
True
402 if hasattr(component,
"async_setup"):
403 task = component.async_setup(hass, processed_config)
404 elif hasattr(component,
"setup"):
407 task = hass.loop.run_in_executor(
408 None, component.setup, hass, processed_config
410 elif not hasattr(component,
"async_setup_entry"):
411 log_error(
"No setup or config entry setup function defined.")
415 async
with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, domain):
420 "Setup of '%s' is taking longer than %s seconds."
421 " Startup will proceed without waiting any longer"
428 except (asyncio.CancelledError, SystemExit, Exception):
429 _LOGGER.exception(
"Error during setup of component %s", domain)
436 log_error(
"Integration failed to initialize.")
438 if result
is not True:
440 f
"Integration {domain!r} did not return boolean if setup was "
441 "successful. Disabling component."
445 if load_translations_task:
446 await load_translations_task
448 if integration.platforms_exists((
"config_flow",)):
452 await hass.config_entries.flow.async_wait_import_flow_initialized(domain)
456 hass.config.components.add(domain)
458 if entries := hass.config_entries.async_entries(
459 domain, include_ignore=
False, include_disabled=
False
461 await asyncio.gather(
464 entry.async_setup_locked(hass, integration=integration),
466 f
"config entry setup {entry.title} {entry.domain} "
476 hass.data[DATA_SETUP].pop(domain,
None)
478 hass.bus.async_fire_internal(
486 hass: core.HomeAssistant, hass_config: ConfigType, domain: str, platform_name: str
487 ) -> ModuleType |
None:
488 """Load a platform and makes sure dependencies are setup.
490 This method is a coroutine.
492 platform_path = PLATFORM_FORMAT.format(domain=domain, platform=platform_name)
498 "Unable to prepare setup for platform '%s': %s", platform_path, msg
503 integration = await loader.async_get_integration(hass, platform_name)
514 if load_top_level_component := integration.domain
not in hass.config.components:
521 except HomeAssistantError
as err:
526 component = await integration.async_get_component()
527 except ImportError
as exc:
528 log_error(f
"Unable to import the component ({exc}).")
531 if not integration.platforms_exists((domain,)):
533 f
"Platform not found (No module named '{integration.pkg_path}.{domain}')"
538 platform = await integration.async_get_platform(domain)
539 except ImportError
as exc:
540 log_error(f
"Platform not found ({exc}).")
544 if platform_path
in hass.config.components:
549 if load_top_level_component:
551 hasattr(component,
"setup")
or hasattr(component,
"async_setup")
562 """Process all dependencies and requirements for a module.
564 Module is a Python module of either a component or platform.
566 if (processed := hass.data.get(DATA_DEPS_REQS))
is None:
567 processed = hass.data[DATA_DEPS_REQS] = set()
568 elif integration.domain
in processed:
574 async
with hass.timeout.async_freeze(integration.domain):
575 await requirements.async_get_integration_with_requirements(
576 hass, integration.domain
579 processed.add(integration.domain)
584 hass: core.HomeAssistant,
588 """Call a method when a component is setup."""
594 hass: core.HomeAssistant,
598 """Call a method when a component is setup or state is fired."""
604 hass: core.HomeAssistant,
609 """Call a method when a component is setup or the start event fires."""
611 async
def when_setup() -> None:
612 """Call the callback."""
614 await when_setup_cb(hass, component)
616 _LOGGER.exception(
"Error handling when_setup callback for %s", component)
618 if component
in hass.config.components:
619 hass.async_create_task_internal(
620 when_setup(), f
"when setup {component}", eager_start=
True
624 listeners: list[CALLBACK_TYPE] = []
626 async
def _matched_event(event: Event[Any]) ->
None:
627 """Call the callback when we matched an event."""
628 for listener
in listeners:
633 def _async_is_component_filter(event_data: EventComponentLoaded) -> bool:
634 """Check if the event is for the component."""
635 return event_data[ATTR_COMPONENT] == component
638 hass.bus.async_listen(
639 EVENT_COMPONENT_LOADED,
641 event_filter=_async_is_component_filter,
646 hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _matched_event)
652 """Return the complete list of loaded integrations."""
653 return hass.config.all_components
657 """Constants for setup time measurements."""
660 """Set up of a component in __init__.py."""
661 CONFIG_ENTRY_SETUP =
"config_entry_setup"
662 """Set up of a config entry in __init__.py."""
663 PLATFORM_SETUP =
"platform_setup"
664 """Set up of a platform integration.
666 ex async_setup_platform or setup_platform or
667 a legacy platform like device_tracker.legacy
669 CONFIG_ENTRY_PLATFORM_SETUP =
"config_entry_platform_setup"
670 """Set up of a platform in a config entry after the config entry is setup.
672 This is only for platforms that are not awaited in async_setup_entry.
674 WAIT_BASE_PLATFORM_SETUP =
"wait_base_component"
675 """Wait time for the base component to be setup."""
676 WAIT_IMPORT_PLATFORMS =
"wait_import_platforms"
677 """Wait time for the platforms to import."""
678 WAIT_IMPORT_PACKAGES =
"wait_import_packages"
679 """Wait time for the packages to import."""
682 @singleton.singleton(DATA_SETUP_STARTED)
684 hass: core.HomeAssistant,
685 ) -> dict[tuple[str, str |
None], float]:
686 """Return the setup started dict."""
690 @contextlib.contextmanager
692 """Keep track of time we are blocked waiting for other operations.
694 We want to count the time we wait for importing and
695 setting up the base components so we can subtract it
696 from the total setup time.
698 if not (running := current_setup_group.get())
or running
not in _setup_started(
708 started = time.monotonic()
712 time_taken = time.monotonic() - started
713 integration, group = running
715 _setup_times(hass)[integration][group][phase] = -time_taken
717 "Adding wait for %s for %s (%s) of %.2f",
725 @singleton.singleton(DATA_SETUP_TIME)
727 hass: core.HomeAssistant,
728 ) -> defaultdict[str, defaultdict[str |
None, defaultdict[SetupPhases, float]]]:
729 """Return the setup timings default dict."""
730 return defaultdict(
lambda: defaultdict(
lambda: defaultdict(float)))
733 @contextlib.contextmanager
735 hass: core.HomeAssistant,
738 group: str |
None =
None,
739 ) -> Generator[
None]:
740 """Keep track of when setup starts and finishes.
742 :param hass: Home Assistant instance
743 :param integration: The integration that is being setup
744 :param phase: The phase of setup
745 :param group: The group (config entry/platform instance) that is being setup
747 A group is a group of setups that run in parallel.
750 if hass.is_stopping
or hass.state
is core.CoreState.running:
758 current = (integration, group)
759 if current
in setup_started:
766 started = time.monotonic()
767 current_setup_group.set(current)
768 setup_started[current] = started
773 time_taken = time.monotonic() - started
774 del setup_started[current]
775 group_setup_times =
_setup_times(hass)[integration][group]
778 group_setup_times[phase] =
max(group_setup_times[phase], time_taken)
781 "Setup of domain %s took %.2f seconds", integration, time_taken
783 elif _LOGGER.isEnabledFor(logging.DEBUG):
784 wait_time = -sum(value
for value
in group_setup_times.values()
if value < 0)
785 calculated_time = time_taken - wait_time
787 "Phase %s for %s (%s) took %.2fs (elapsed=%.2fs) (wait_time=%.2fs)",
799 """Return timing data for each integration."""
801 domain_timings: dict[str, float] = {}
802 top_level_timings: Mapping[SetupPhases, float]
803 for domain, timings
in setup_time.items():
804 top_level_timings = timings.get(
None, {})
805 total_top_level = sum(top_level_timings.values())
809 group: sum(group_timings.values())
810 for group, group_timings
in timings.items()
813 group_max =
max(group_totals.values(), default=0)
814 domain_timings[domain] = total_top_level + group_max
816 return domain_timings
821 hass: core.HomeAssistant, domain: str
822 ) -> Mapping[str |
None, dict[SetupPhases, float]]:
823 """Return timing data for each integration."""
web.Response get(self, web.Request request, str config_key)
None async_create_issue(HomeAssistant hass, str entry_id)
ModuleType|None async_prepare_setup_platform(core.HomeAssistant hass, ConfigType hass_config, str domain, str platform_name)
None async_when_setup_or_start(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb)
Generator[None] async_pause_setup(core.HomeAssistant hass, SetupPhases phase)
None async_when_setup(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb)
set[str] async_get_loaded_integrations(core.HomeAssistant hass)
None _log_error_setup_error(HomeAssistant hass, str domain, loader.Integration|None integration, str msg, Exception|None exc_info=None)
None async_notify_setup_error(HomeAssistant hass, str component, str|None display_link=None)
None async_set_domains_to_be_loaded(core.HomeAssistant hass, set[str] domains)
bool _async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)
bool setup_component(core.HomeAssistant hass, str domain, ConfigType config)
defaultdict[str, defaultdict[str|None, defaultdict[SetupPhases, float]]] _setup_times(core.HomeAssistant hass)
dict[str, float] async_get_setup_timings(core.HomeAssistant hass)
Generator[None] async_start_setup(core.HomeAssistant hass, str integration, SetupPhases phase, str|None group=None)
list[str] _async_process_dependencies(core.HomeAssistant hass, ConfigType config, loader.Integration integration)
dict[tuple[str, str|None], float] _setup_started(core.HomeAssistant hass)
Mapping[str|None, dict[SetupPhases, float]] async_get_domain_setup_times(core.HomeAssistant hass, str domain)
None async_process_deps_reqs(core.HomeAssistant hass, ConfigType config, loader.Integration integration)
None _async_when_setup(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb, bool start_event)
bool async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)