Home Assistant Unofficial Reference 2024.12.1
util.py
Go to the documentation of this file.
1 """Support for script and automation tracing and debugging."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 import logging
7 from typing import Any
8 
9 from homeassistant.core import HomeAssistant
10 from homeassistant.exceptions import HomeAssistantError
11 from homeassistant.util.limited_size_dict import LimitedSizeDict
12 
13 from .const import DATA_TRACE, DATA_TRACE_STORE, DATA_TRACES_RESTORED
14 from .models import ActionTrace, BaseTrace, RestoredTrace, TraceData
15 
16 _LOGGER = logging.getLogger(__name__)
17 
18 
19 async def async_get_trace(
20  hass: HomeAssistant, key: str, run_id: str
21 ) -> dict[str, BaseTrace]:
22  """Return the requested trace."""
23  # Restore saved traces if not done
24  await async_restore_traces(hass)
25 
26  return hass.data[DATA_TRACE][key][run_id].as_extended_dict()
27 
28 
30  hass: HomeAssistant, key: str | None
31 ) -> dict[str, dict[str, str]]:
32  """List contexts for which we have traces."""
33  # Restore saved traces if not done
34  await async_restore_traces(hass)
35 
36  values: Mapping[str, LimitedSizeDict[str, BaseTrace] | None] | TraceData
37  if key is not None:
38  values = {key: hass.data[DATA_TRACE].get(key)}
39  else:
40  values = hass.data[DATA_TRACE]
41 
42  def _trace_id(run_id: str, key: str) -> dict[str, str]:
43  """Make trace_id for the response."""
44  domain, item_id = key.split(".", 1)
45  return {"run_id": run_id, "domain": domain, "item_id": item_id}
46 
47  return {
48  trace.context.id: _trace_id(trace.run_id, key)
49  for key, traces in values.items()
50  if traces is not None
51  for trace in traces.values()
52  }
53 
54 
55 def _get_debug_traces(hass: HomeAssistant, key: str) -> list[dict[str, Any]]:
56  """Return a serializable list of debug traces for a script or automation."""
57  if traces_for_key := hass.data[DATA_TRACE].get(key):
58  return [trace.as_short_dict() for trace in traces_for_key.values()]
59  return []
60 
61 
63  hass: HomeAssistant, wanted_domain: str, wanted_key: str | None
64 ) -> list[dict[str, Any]]:
65  """List traces for a domain."""
66  # Restore saved traces if not done already
67  await async_restore_traces(hass)
68 
69  if not wanted_key:
70  traces: list[dict[str, Any]] = []
71  for key in hass.data[DATA_TRACE]:
72  domain = key.split(".", 1)[0]
73  if domain == wanted_domain:
74  traces.extend(_get_debug_traces(hass, key))
75  else:
76  traces = _get_debug_traces(hass, wanted_key)
77 
78  return traces
79 
80 
82  hass: HomeAssistant, trace: ActionTrace, stored_traces: int
83 ) -> None:
84  """Store a trace if its key is valid."""
85  if key := trace.key:
86  traces = hass.data[DATA_TRACE]
87  if key not in traces:
88  traces[key] = LimitedSizeDict(size_limit=stored_traces)
89  else:
90  traces[key].size_limit = stored_traces
91  traces[key][trace.run_id] = trace
92 
93 
94 def _async_store_restored_trace(hass: HomeAssistant, trace: RestoredTrace) -> None:
95  """Store a restored trace and move it to the end of the LimitedSizeDict."""
96  key = trace.key
97  traces = hass.data[DATA_TRACE]
98  if key not in traces:
99  traces[key] = LimitedSizeDict()
100  traces[key][trace.run_id] = trace
101  traces[key].move_to_end(trace.run_id, last=False)
102 
103 
104 async def async_restore_traces(hass: HomeAssistant) -> None:
105  """Restore saved traces."""
106  if DATA_TRACES_RESTORED in hass.data:
107  return
108 
109  hass.data[DATA_TRACES_RESTORED] = True
110 
111  store = hass.data[DATA_TRACE_STORE]
112  try:
113  restored_traces = await store.async_load() or {}
114  except HomeAssistantError:
115  _LOGGER.exception("Error loading traces")
116  restored_traces = {}
117 
118  for key, traces in restored_traces.items():
119  # Add stored traces in reversed order to prioritize the newest traces
120  for json_trace in reversed(traces):
121  if (
122  (stored_traces := hass.data[DATA_TRACE].get(key))
123  and stored_traces.size_limit is not None
124  and len(stored_traces) >= stored_traces.size_limit
125  ):
126  break
127 
128  try:
129  trace = RestoredTrace(json_trace)
130  # Catch any exception to not blow up if the stored trace is invalid
131  except Exception:
132  _LOGGER.exception("Failed to restore trace")
133  continue
134  _async_store_restored_trace(hass, trace)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None _async_store_restored_trace(HomeAssistant hass, RestoredTrace trace)
Definition: util.py:94
list[dict[str, Any]] _get_debug_traces(HomeAssistant hass, str key)
Definition: util.py:55
None async_store_trace(HomeAssistant hass, ActionTrace trace, int stored_traces)
Definition: util.py:83
dict[str, BaseTrace] async_get_trace(HomeAssistant hass, str key, str run_id)
Definition: util.py:21
None async_restore_traces(HomeAssistant hass)
Definition: util.py:104
dict[str, dict[str, str]] async_list_contexts(HomeAssistant hass, str|None key)
Definition: util.py:31
list[dict[str, Any]] async_list_traces(HomeAssistant hass, str wanted_domain, str|None wanted_key)
Definition: util.py:64