Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Provide pre-made queries on top of the recorder component."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime as dt, timedelta
6 from http import HTTPStatus
7 from typing import cast
8 
9 from aiohttp import web
10 import voluptuous as vol
11 
12 from homeassistant.components import frontend
13 from homeassistant.components.http import KEY_HASS, HomeAssistantView
14 from homeassistant.components.recorder import get_instance, history
15 from homeassistant.components.recorder.util import session_scope
16 from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
17 from homeassistant.core import HomeAssistant, valid_entity_id
19 from homeassistant.helpers.entityfilter import INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA
20 from homeassistant.helpers.typing import ConfigType
21 import homeassistant.util.dt as dt_util
22 
23 from . import websocket_api
24 from .const import DOMAIN
25 from .helpers import entities_may_have_state_changes_after, has_states_before
26 
27 CONF_ORDER = "use_include_order"
28 
29 _ONE_DAY = timedelta(days=1)
30 
31 CONFIG_SCHEMA = vol.Schema(
32  {
33  DOMAIN: vol.All(
34  cv.deprecated(CONF_INCLUDE),
35  cv.deprecated(CONF_EXCLUDE),
36  cv.deprecated(CONF_ORDER),
37  INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend(
38  {vol.Optional(CONF_ORDER, default=False): cv.boolean}
39  ),
40  )
41  },
42  extra=vol.ALLOW_EXTRA,
43 )
44 
45 
46 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
47  """Set up the history hooks."""
48  hass.http.register_view(HistoryPeriodView())
49  frontend.async_register_built_in_panel(hass, "history", "history", "hass:chart-box")
50  websocket_api.async_setup(hass)
51  return True
52 
53 
54 class HistoryPeriodView(HomeAssistantView):
55  """Handle history period requests."""
56 
57  url = "/api/history/period"
58  name = "api:history:view-period"
59  extra_urls = ["/api/history/period/{datetime}"]
60 
61  async def get(
62  self, request: web.Request, datetime: str | None = None
63  ) -> web.Response:
64  """Return history over a period of time."""
65  datetime_ = None
66  query = request.query
67 
68  if datetime and (datetime_ := dt_util.parse_datetime(datetime)) is None:
69  return self.json_message("Invalid datetime", HTTPStatus.BAD_REQUEST)
70 
71  if not (entity_ids_str := query.get("filter_entity_id")) or not (
72  entity_ids := entity_ids_str.strip().lower().split(",")
73  ):
74  return self.json_message(
75  "filter_entity_id is missing", HTTPStatus.BAD_REQUEST
76  )
77 
78  hass = request.app[KEY_HASS]
79 
80  for entity_id in entity_ids:
81  if not hass.states.get(entity_id) and not valid_entity_id(entity_id):
82  return self.json_message(
83  "Invalid filter_entity_id", HTTPStatus.BAD_REQUEST
84  )
85 
86  now = dt_util.utcnow()
87  if datetime_:
88  start_time = dt_util.as_utc(datetime_)
89  else:
90  start_time = now - _ONE_DAY
91 
92  if start_time > now:
93  return self.json([])
94 
95  if end_time_str := query.get("end_time"):
96  if end_time := dt_util.parse_datetime(end_time_str):
97  end_time = dt_util.as_utc(end_time)
98  else:
99  return self.json_message("Invalid end_time", HTTPStatus.BAD_REQUEST)
100  else:
101  end_time = start_time + _ONE_DAY
102 
103  include_start_time_state = "skip_initial_state" not in query
104  significant_changes_only = query.get("significant_changes_only", "1") != "0"
105 
106  minimal_response = "minimal_response" in request.query
107  no_attributes = "no_attributes" in request.query
108 
109  if (
110  # has_states_before will return True if there are states older than
111  # end_time. If it's false, we know there are no states in the
112  # database up until end_time.
113  (end_time and not has_states_before(hass, end_time))
114  or not include_start_time_state
115  and entity_ids
117  hass, entity_ids, start_time, no_attributes
118  )
119  ):
120  return self.json([])
121 
122  return cast(
123  web.Response,
124  await get_instance(hass).async_add_executor_job(
125  self._sorted_significant_states_json_sorted_significant_states_json,
126  hass,
127  start_time,
128  end_time,
129  entity_ids,
130  include_start_time_state,
131  significant_changes_only,
132  minimal_response,
133  no_attributes,
134  ),
135  )
136 
138  self,
139  hass: HomeAssistant,
140  start_time: dt,
141  end_time: dt,
142  entity_ids: list[str],
143  include_start_time_state: bool,
144  significant_changes_only: bool,
145  minimal_response: bool,
146  no_attributes: bool,
147  ) -> web.Response:
148  """Fetch significant stats from the database as json."""
149  with session_scope(hass=hass, read_only=True) as session:
150  return self.json(
151  list(
152  history.get_significant_states_with_session(
153  hass,
154  session,
155  start_time,
156  end_time,
157  entity_ids,
158  None,
159  include_start_time_state,
160  significant_changes_only,
161  minimal_response,
162  no_attributes,
163  ).values()
164  )
165  )
web.Response get(self, web.Request request, str|None datetime=None)
Definition: __init__.py:63
web.Response _sorted_significant_states_json(self, HomeAssistant hass, dt start_time, dt end_time, list[str] entity_ids, bool include_start_time_state, bool significant_changes_only, bool minimal_response, bool no_attributes)
Definition: __init__.py:147
bool has_states_before(HomeAssistant hass, dt run_time)
Definition: helpers.py:28
bool entities_may_have_state_changes_after(HomeAssistant hass, Iterable entity_ids, dt start_time, bool no_attributes)
Definition: helpers.py:14
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:46
Recorder get_instance(HomeAssistant hass)
Definition: recorder.py:74
Generator[Session] session_scope(*HomeAssistant|None hass=None, Session|None session=None, Callable[[Exception], bool]|None exception_filter=None, bool read_only=False)
Definition: recorder.py:86