Home Assistant Unofficial Reference 2024.12.1
setup.py
Go to the documentation of this file.
1 """All methods needed to bootstrap a Home Assistant instance."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections import defaultdict
7 from collections.abc import Awaitable, Callable, Generator, Mapping
8 import contextlib
9 import contextvars
10 from enum import StrEnum
11 from functools import partial
12 import logging.handlers
13 import time
14 from types import ModuleType
15 from typing import Any, Final, TypedDict
16 
17 from . import config as conf_util, core, loader, requirements
18 from .const import (
19  BASE_PLATFORMS, # noqa: F401
20  EVENT_COMPONENT_LOADED,
21  EVENT_HOMEASSISTANT_START,
22  PLATFORM_FORMAT,
23 )
24 from .core import (
25  CALLBACK_TYPE,
26  DOMAIN as HOMEASSISTANT_DOMAIN,
27  Event,
28  HomeAssistant,
29  callback,
30 )
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
37 
38 current_setup_group: contextvars.ContextVar[tuple[str, str | None] | None] = (
39  contextvars.ContextVar("current_setup_group", default=None)
40 )
41 
42 
43 _LOGGER = logging.getLogger(__name__)
44 
45 ATTR_COMPONENT: Final = "component"
46 
47 
48 # DATA_SETUP is a dict, indicating domains which are currently
49 # being setup or which failed to setup:
50 # - Tasks are added to DATA_SETUP by `async_setup_component`, the key is the domain
51 # being setup and the Task is the `_async_setup_component` helper.
52 # - Tasks are removed from DATA_SETUP if setup was successful, that is,
53 # the task returned True.
54 DATA_SETUP: HassKey[dict[str, asyncio.Future[bool]]] = HassKey("setup_tasks")
55 
56 # DATA_SETUP_DONE is a dict, indicating components which will be setup:
57 # - Events are added to DATA_SETUP_DONE during bootstrap by
58 # async_set_domains_to_be_loaded, the key is the domain which will be loaded.
59 # - Events are set and removed from DATA_SETUP_DONE when async_setup_component
60 # is finished, regardless of if the setup was successful or not.
61 DATA_SETUP_DONE: HassKey[dict[str, asyncio.Future[bool]]] = HassKey("setup_done")
62 
63 # DATA_SETUP_STARTED is a dict, indicating when an attempt
64 # to setup a component started.
65 DATA_SETUP_STARTED: HassKey[dict[tuple[str, str | None], float]] = HassKey(
66  "setup_started"
67 )
68 
69 # DATA_SETUP_TIME is a defaultdict, indicating how time was spent
70 # setting up a component.
71 DATA_SETUP_TIME: HassKey[
72  defaultdict[str, defaultdict[str | None, defaultdict[SetupPhases, float]]]
73 ] = HassKey("setup_time")
74 
75 DATA_DEPS_REQS: HassKey[set[str]] = HassKey("deps_reqs_processed")
76 
77 DATA_PERSISTENT_ERRORS: HassKey[dict[str, str | None]] = HassKey(
78  "bootstrap_persistent_errors"
79 )
80 
81 NOTIFY_FOR_TRANSLATION_KEYS = [
82  "config_validation_err",
83  "platform_config_validation_err",
84 ]
85 
86 SLOW_SETUP_WARNING = 10
87 SLOW_SETUP_MAX_WAIT = 300
88 
89 
90 class EventComponentLoaded(TypedDict):
91  """EventComponentLoaded data."""
92 
93  component: str
94 
95 
96 @callback
98  hass: HomeAssistant, component: str, display_link: str | None = None
99 ) -> None:
100  """Print a persistent notification.
101 
102  This method must be run in the event loop.
103  """
104  # pylint: disable-next=import-outside-toplevel
105  from .components import persistent_notification
106 
107  if (errors := hass.data.get(DATA_PERSISTENT_ERRORS)) is None:
108  errors = hass.data[DATA_PERSISTENT_ERRORS] = {}
109 
110  errors[component] = errors.get(component) or display_link
111 
112  message = "The following integrations and platforms could not be set up:\n\n"
113 
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"
118 
119  message += "\nPlease check your config and [logs](/config/logs)."
120 
121  persistent_notification.async_create(
122  hass, message, "Invalid config", "invalid_config"
123  )
124 
125 
126 @core.callback
127 def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: set[str]) -> None:
128  """Set domains that are going to be loaded from the config.
129 
130  This allow us to:
131  - Properly handle after_dependencies.
132  - Keep track of domains which will load but have not yet finished loading
133  """
134  setup_done_futures = hass.data.setdefault(DATA_SETUP_DONE, {})
135  setup_done_futures.update({domain: hass.loop.create_future() for domain in domains})
136 
137 
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(
141  async_setup_component(hass, domain, config), hass.loop
142  ).result()
143 
144 
146  hass: core.HomeAssistant, domain: str, config: ConfigType
147 ) -> bool:
148  """Set up a component and all its dependencies.
149 
150  This method is a coroutine.
151  """
152  if domain in hass.config.components:
153  return True
154 
155  setup_futures = hass.data.setdefault(DATA_SETUP, {})
156  setup_done_futures = hass.data.setdefault(DATA_SETUP_DONE, {})
157 
158  if existing_setup_future := setup_futures.get(domain):
159  return await existing_setup_future
160 
161  setup_future = hass.loop.create_future()
162  setup_futures[domain] = setup_future
163 
164  try:
165  result = await _async_setup_component(hass, domain, config)
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:
174  # If the setup call is cancelled it likely means
175  # Home Assistant is shutting down so the future might
176  # already be done which will cause this to raise
177  # an InvalidStateError which is appropriate because
178  # the component setup was cancelled and is in an
179  # indeterminate state.
180  future.set_exception(err)
181  with contextlib.suppress(BaseException):
182  # Clear the flag as its normal that nothing
183  # will wait for this future to be resolved
184  # if there are no concurrent setup attempts
185  await future
186  raise
187  return result
188 
189 
191  hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
192 ) -> list[str]:
193  """Ensure all dependencies are set up.
194 
195  Returns a list of dependencies which failed to set up.
196  """
197  setup_futures = hass.data.setdefault(DATA_SETUP, {})
198 
199  dependencies_tasks = {
200  dep: setup_futures.get(dep)
201  or create_eager_task(
202  async_setup_component(hass, dep, config),
203  name=f"setup {dep} as dependency of {integration.domain}",
204  loop=hass.loop,
205  )
206  for dep in integration.dependencies
207  if dep not in hass.config.components
208  }
209 
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:
213  if (
214  dep not in dependencies_tasks
215  and dep in to_be_loaded
216  and dep not in hass.config.components
217  ):
218  after_dependencies_tasks[dep] = to_be_loaded[dep]
219 
220  if not dependencies_tasks and not after_dependencies_tasks:
221  return []
222 
223  if dependencies_tasks:
224  _LOGGER.debug(
225  "Dependency %s will wait for dependencies %s",
226  integration.domain,
227  dependencies_tasks.keys(),
228  )
229  if after_dependencies_tasks:
230  _LOGGER.debug(
231  "Dependency %s will wait for after dependencies %s",
232  integration.domain,
233  after_dependencies_tasks.keys(),
234  )
235 
236  async with hass.timeout.async_freeze(integration.domain):
237  results = await asyncio.gather(
238  *dependencies_tasks.values(), *after_dependencies_tasks.values()
239  )
240 
241  failed = [
242  domain for idx, domain in enumerate(dependencies_tasks) if not results[idx]
243  ]
244 
245  if failed:
246  _LOGGER.error(
247  "Unable to set up dependencies of '%s'. Setup failed for dependencies: %s",
248  integration.domain,
249  failed,
250  )
251 
252  return failed
253 
254 
256  hass: HomeAssistant,
257  domain: str,
258  integration: loader.Integration | None,
259  msg: str,
260  exc_info: Exception | None = None,
261 ) -> None:
262  """Log helper."""
263  if integration is None:
264  custom = ""
265  link = None
266  else:
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)
270  async_notify_setup_error(hass, domain, link)
271 
272 
274  hass: core.HomeAssistant, domain: str, config: ConfigType
275 ) -> bool:
276  """Set up a component for Home Assistant.
277 
278  This method is a coroutine.
279  """
280  try:
281  integration = await loader.async_get_integration(hass, domain)
283  _log_error_setup_error(hass, domain, None, "Integration not found.")
284  if not hass.config.safe_mode and hass.config_entries.async_entries(domain):
285  ir.async_create_issue(
286  hass,
287  HOMEASSISTANT_DOMAIN,
288  f"integration_not_found.{domain}",
289  is_fixable=True,
290  issue_domain=HOMEASSISTANT_DOMAIN,
291  severity=IssueSeverity.ERROR,
292  translation_key="integration_not_found",
293  translation_placeholders={
294  "domain": domain,
295  },
296  data={"domain": domain},
297  )
298  return False
299 
300  log_error = partial(_log_error_setup_error, hass, domain, integration)
301 
302  if integration.disabled:
303  log_error(f"Dependency is disabled - {integration.disabled}")
304  return False
305 
306  integration_set = {domain}
307 
308  load_translations_task: asyncio.Task[None] | None = None
309  if integration.has_translations and not translation.async_translations_loaded(
310  hass, integration_set
311  ):
312  # For most cases we expect the translations are already
313  # loaded since we try to load them in bootstrap ahead of time.
314  # If for some reason the background task in bootstrap was too slow
315  # or the integration was added after bootstrap, we will load them here.
316  load_translations_task = create_eager_task(
317  translation.async_load_integrations(hass, integration_set), loop=hass.loop
318  )
319  # Validate all dependencies exist and there are no circular dependencies
320  if not await integration.resolve_dependencies():
321  return False
322 
323  # Process requirements as soon as possible, so we can import the component
324  # without requiring imports to be in functions.
325  try:
326  await async_process_deps_reqs(hass, config, integration)
327  except HomeAssistantError as err:
328  log_error(str(err))
329  return False
330 
331  # Some integrations fail on import because they call functions incorrectly.
332  # So we do it before validating config to catch these errors.
333  try:
334  component = await integration.async_get_component()
335  except ImportError as err:
336  log_error(f"Unable to import component: {err}", err)
337  return False
338 
339  integration_config_info = await conf_util.async_process_component_config(
340  hass, config, integration, component
341  )
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
345  )
346  for platform_exception in integration_config_info.exception_info_list:
347  if platform_exception.translation_key not in NOTIFY_FOR_TRANSLATION_KEYS:
348  continue
350  hass, platform_exception.platform_path, platform_exception.integration_link
351  )
352  if processed_config is None:
353  log_error("Invalid config.")
354  return False
355 
356  # Detect attempt to setup integration which can be setup only from config entry
357  if (
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")
362  ):
363  _LOGGER.error(
364  (
365  "The '%s' integration does not support YAML setup, please remove it "
366  "from your configuration"
367  ),
368  domain,
369  )
371  hass,
372  HOMEASSISTANT_DOMAIN,
373  f"config_entry_only_{domain}",
374  is_fixable=False,
375  severity=IssueSeverity.ERROR,
376  issue_domain=domain,
377  translation_key="config_entry_only",
378  translation_placeholders={
379  "domain": domain,
380  "add_integration": f"/config/integrations/dashboard/add?domain={domain}",
381  },
382  )
383 
384  _LOGGER.info("Setting up %s", domain)
385 
386  with async_start_setup(hass, integration=domain, phase=SetupPhases.SETUP):
387  if hasattr(component, "PLATFORM_SCHEMA"):
388  # Entity components have their own warning
389  warn_task = None
390  else:
391  warn_task = hass.loop.call_later(
392  SLOW_SETUP_WARNING,
393  _LOGGER.warning,
394  "Setup of %s is taking over %s seconds.",
395  domain,
396  SLOW_SETUP_WARNING,
397  )
398 
399  task: Awaitable[bool] | None = None
400  result: Any | bool = True
401  try:
402  if hasattr(component, "async_setup"):
403  task = component.async_setup(hass, processed_config)
404  elif hasattr(component, "setup"):
405  # This should not be replaced with hass.async_add_executor_job because
406  # we don't want to track this task in case it blocks startup.
407  task = hass.loop.run_in_executor(
408  None, component.setup, hass, processed_config
409  )
410  elif not hasattr(component, "async_setup_entry"):
411  log_error("No setup or config entry setup function defined.")
412  return False
413 
414  if task:
415  async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, domain):
416  result = await task
417  except TimeoutError:
418  _LOGGER.error(
419  (
420  "Setup of '%s' is taking longer than %s seconds."
421  " Startup will proceed without waiting any longer"
422  ),
423  domain,
424  SLOW_SETUP_MAX_WAIT,
425  )
426  return False
427  # pylint: disable-next=broad-except
428  except (asyncio.CancelledError, SystemExit, Exception):
429  _LOGGER.exception("Error during setup of component %s", domain)
430  async_notify_setup_error(hass, domain, integration.documentation)
431  return False
432  finally:
433  if warn_task:
434  warn_task.cancel()
435  if result is False:
436  log_error("Integration failed to initialize.")
437  return False
438  if result is not True:
439  log_error(
440  f"Integration {domain!r} did not return boolean if setup was "
441  "successful. Disabling component."
442  )
443  return False
444 
445  if load_translations_task:
446  await load_translations_task
447 
448  if integration.platforms_exists(("config_flow",)):
449  # If the integration has a config_flow, wait for import flows.
450  # As these are all created with eager tasks, we do not sleep here,
451  # as the tasks will always be started before we reach this point.
452  await hass.config_entries.flow.async_wait_import_flow_initialized(domain)
453 
454  # Add to components before the entry.async_setup
455  # call to avoid a deadlock when forwarding platforms
456  hass.config.components.add(domain)
457 
458  if entries := hass.config_entries.async_entries(
459  domain, include_ignore=False, include_disabled=False
460  ):
461  await asyncio.gather(
462  *(
463  create_eager_task(
464  entry.async_setup_locked(hass, integration=integration),
465  name=(
466  f"config entry setup {entry.title} {entry.domain} "
467  f"{entry.entry_id}"
468  ),
469  loop=hass.loop,
470  )
471  for entry in entries
472  )
473  )
474 
475  # Cleanup
476  hass.data[DATA_SETUP].pop(domain, None)
477 
478  hass.bus.async_fire_internal(
479  EVENT_COMPONENT_LOADED, EventComponentLoaded(component=domain)
480  )
481 
482  return True
483 
484 
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.
489 
490  This method is a coroutine.
491  """
492  platform_path = PLATFORM_FORMAT.format(domain=domain, platform=platform_name)
493 
494  def log_error(msg: str) -> None:
495  """Log helper."""
496 
497  _LOGGER.error(
498  "Unable to prepare setup for platform '%s': %s", platform_path, msg
499  )
500  async_notify_setup_error(hass, platform_path)
501 
502  try:
503  integration = await loader.async_get_integration(hass, platform_name)
505  log_error("Integration not found")
506  return None
507 
508  # Platforms cannot exist on their own, they are part of their integration.
509  # If the integration is not set up yet, and can be set up, set it up.
510  #
511  # We do this before we import the platform so the platform already knows
512  # where the top level component is.
513  #
514  if load_top_level_component := integration.domain not in hass.config.components:
515  # Process deps and reqs as soon as possible, so that requirements are
516  # available when we import the platform. We only do this if the integration
517  # is not in hass.config.components yet, as we already processed them in
518  # async_setup_component if it is.
519  try:
520  await async_process_deps_reqs(hass, hass_config, integration)
521  except HomeAssistantError as err:
522  log_error(str(err))
523  return None
524 
525  try:
526  component = await integration.async_get_component()
527  except ImportError as exc:
528  log_error(f"Unable to import the component ({exc}).")
529  return None
530 
531  if not integration.platforms_exists((domain,)):
532  log_error(
533  f"Platform not found (No module named '{integration.pkg_path}.{domain}')"
534  )
535  return None
536 
537  try:
538  platform = await integration.async_get_platform(domain)
539  except ImportError as exc:
540  log_error(f"Platform not found ({exc}).")
541  return None
542 
543  # Already loaded
544  if platform_path in hass.config.components:
545  return platform
546 
547  # Platforms cannot exist on their own, they are part of their integration.
548  # If the integration is not set up yet, and can be set up, set it up.
549  if load_top_level_component:
550  if (
551  hasattr(component, "setup") or hasattr(component, "async_setup")
552  ) and not await async_setup_component(hass, integration.domain, hass_config):
553  log_error("Unable to set up component.")
554  return None
555 
556  return platform
557 
558 
560  hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
561 ) -> None:
562  """Process all dependencies and requirements for a module.
563 
564  Module is a Python module of either a component or platform.
565  """
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:
569  return
570 
571  if failed_deps := await _async_process_dependencies(hass, config, integration):
572  raise DependencyError(failed_deps)
573 
574  async with hass.timeout.async_freeze(integration.domain):
575  await requirements.async_get_integration_with_requirements(
576  hass, integration.domain
577  )
578 
579  processed.add(integration.domain)
580 
581 
582 @core.callback
584  hass: core.HomeAssistant,
585  component: str,
586  when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
587 ) -> None:
588  """Call a method when a component is setup."""
589  _async_when_setup(hass, component, when_setup_cb, False)
590 
591 
592 @core.callback
594  hass: core.HomeAssistant,
595  component: str,
596  when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
597 ) -> None:
598  """Call a method when a component is setup or state is fired."""
599  _async_when_setup(hass, component, when_setup_cb, True)
600 
601 
602 @core.callback
604  hass: core.HomeAssistant,
605  component: str,
606  when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
607  start_event: bool,
608 ) -> None:
609  """Call a method when a component is setup or the start event fires."""
610 
611  async def when_setup() -> None:
612  """Call the callback."""
613  try:
614  await when_setup_cb(hass, component)
615  except Exception:
616  _LOGGER.exception("Error handling when_setup callback for %s", component)
617 
618  if component in hass.config.components:
619  hass.async_create_task_internal(
620  when_setup(), f"when setup {component}", eager_start=True
621  )
622  return
623 
624  listeners: list[CALLBACK_TYPE] = []
625 
626  async def _matched_event(event: Event[Any]) -> None:
627  """Call the callback when we matched an event."""
628  for listener in listeners:
629  listener()
630  await when_setup()
631 
632  @callback
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
636 
637  listeners.append(
638  hass.bus.async_listen(
639  EVENT_COMPONENT_LOADED,
640  _matched_event,
641  event_filter=_async_is_component_filter,
642  )
643  )
644  if start_event:
645  listeners.append(
646  hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _matched_event)
647  )
648 
649 
650 @core.callback
651 def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]:
652  """Return the complete list of loaded integrations."""
653  return hass.config.all_components
654 
655 
656 class SetupPhases(StrEnum):
657  """Constants for setup time measurements."""
658 
659  SETUP = "setup"
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.
665 
666  ex async_setup_platform or setup_platform or
667  a legacy platform like device_tracker.legacy
668  """
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.
671 
672  This is only for platforms that are not awaited in async_setup_entry.
673  """
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."""
680 
681 
682 @singleton.singleton(DATA_SETUP_STARTED)
684  hass: core.HomeAssistant,
685 ) -> dict[tuple[str, str | None], float]:
686  """Return the setup started dict."""
687  return {}
688 
689 
690 @contextlib.contextmanager
691 def async_pause_setup(hass: core.HomeAssistant, phase: SetupPhases) -> Generator[None]:
692  """Keep track of time we are blocked waiting for other operations.
693 
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.
697  """
698  if not (running := current_setup_group.get()) or running not in _setup_started(
699  hass
700  ):
701  # This means we are likely in a late platform setup
702  # that is running in a task so we do not want
703  # to subtract out the time later as nothing is waiting
704  # for the code inside the context manager to finish.
705  yield
706  return
707 
708  started = time.monotonic()
709  try:
710  yield
711  finally:
712  time_taken = time.monotonic() - started
713  integration, group = running
714  # Add negative time for the time we waited
715  _setup_times(hass)[integration][group][phase] = -time_taken
716  _LOGGER.debug(
717  "Adding wait for %s for %s (%s) of %.2f",
718  phase,
719  integration,
720  group,
721  time_taken,
722  )
723 
724 
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)))
731 
732 
733 @contextlib.contextmanager
735  hass: core.HomeAssistant,
736  integration: str,
737  phase: SetupPhases,
738  group: str | None = None,
739 ) -> Generator[None]:
740  """Keep track of when setup starts and finishes.
741 
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
746 
747  A group is a group of setups that run in parallel.
748 
749  """
750  if hass.is_stopping or hass.state is core.CoreState.running:
751  # Don't track setup times when we are shutting down or already running
752  # as we present the timings as "Integration startup time", and we
753  # don't want to add all the setup retry times to that.
754  yield
755  return
756 
757  setup_started = _setup_started(hass)
758  current = (integration, group)
759  if current in setup_started:
760  # We are already inside another async_start_setup, this like means we
761  # are setting up a platform inside async_setup_entry so we should not
762  # record this as a new setup
763  yield
764  return
765 
766  started = time.monotonic()
767  current_setup_group.set(current)
768  setup_started[current] = started
769 
770  try:
771  yield
772  finally:
773  time_taken = time.monotonic() - started
774  del setup_started[current]
775  group_setup_times = _setup_times(hass)[integration][group]
776  # We may see the phase multiple times if there are multiple
777  # platforms, but we only care about the longest time.
778  group_setup_times[phase] = max(group_setup_times[phase], time_taken)
779  if group is None:
780  _LOGGER.info(
781  "Setup of domain %s took %.2f seconds", integration, time_taken
782  )
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
786  _LOGGER.debug(
787  "Phase %s for %s (%s) took %.2fs (elapsed=%.2fs) (wait_time=%.2fs)",
788  phase,
789  integration,
790  group,
791  calculated_time,
792  time_taken,
793  wait_time,
794  )
795 
796 
797 @callback
798 def async_get_setup_timings(hass: core.HomeAssistant) -> dict[str, float]:
799  """Return timing data for each integration."""
800  setup_time = _setup_times(hass)
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())
806  # Groups (config entries/platform instance) are setup in parallel so we
807  # take the max of the group timings and add it to the top level
808  group_totals = {
809  group: sum(group_timings.values())
810  for group, group_timings in timings.items()
811  if group is not None
812  }
813  group_max = max(group_totals.values(), default=0)
814  domain_timings[domain] = total_top_level + group_max
815 
816  return domain_timings
817 
818 
819 @callback
821  hass: core.HomeAssistant, domain: str
822 ) -> Mapping[str | None, dict[SetupPhases, float]]:
823  """Return timing data for each integration."""
824  return _setup_times(hass).get(domain, {})
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69
ModuleType|None async_prepare_setup_platform(core.HomeAssistant hass, ConfigType hass_config, str domain, str platform_name)
Definition: setup.py:487
None async_when_setup_or_start(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb)
Definition: setup.py:597
Generator[None] async_pause_setup(core.HomeAssistant hass, SetupPhases phase)
Definition: setup.py:691
None async_when_setup(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb)
Definition: setup.py:587
set[str] async_get_loaded_integrations(core.HomeAssistant hass)
Definition: setup.py:651
None _log_error_setup_error(HomeAssistant hass, str domain, loader.Integration|None integration, str msg, Exception|None exc_info=None)
Definition: setup.py:261
None async_notify_setup_error(HomeAssistant hass, str component, str|None display_link=None)
Definition: setup.py:99
None async_set_domains_to_be_loaded(core.HomeAssistant hass, set[str] domains)
Definition: setup.py:127
bool _async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)
Definition: setup.py:275
bool setup_component(core.HomeAssistant hass, str domain, ConfigType config)
Definition: setup.py:138
defaultdict[str, defaultdict[str|None, defaultdict[SetupPhases, float]]] _setup_times(core.HomeAssistant hass)
Definition: setup.py:728
dict[str, float] async_get_setup_timings(core.HomeAssistant hass)
Definition: setup.py:798
Generator[None] async_start_setup(core.HomeAssistant hass, str integration, SetupPhases phase, str|None group=None)
Definition: setup.py:739
list[str] _async_process_dependencies(core.HomeAssistant hass, ConfigType config, loader.Integration integration)
Definition: setup.py:192
dict[tuple[str, str|None], float] _setup_started(core.HomeAssistant hass)
Definition: setup.py:685
Mapping[str|None, dict[SetupPhases, float]] async_get_domain_setup_times(core.HomeAssistant hass, str domain)
Definition: setup.py:822
None async_process_deps_reqs(core.HomeAssistant hass, ConfigType config, loader.Integration integration)
Definition: setup.py:561
None _async_when_setup(core.HomeAssistant hass, str component, Callable[[core.HomeAssistant, str], Awaitable[None]] when_setup_cb, bool start_event)
Definition: setup.py:608
bool async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)
Definition: setup.py:147