Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Support for Google Calendar Search binary sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Iterable
6 from datetime import datetime, timedelta
7 import itertools
8 import logging
9 
10 from gcal_sync.api import GoogleCalendarService, ListEventsRequest
11 from gcal_sync.exceptions import ApiException
12 from gcal_sync.model import Event
13 from gcal_sync.sync import CalendarEventSyncManager
14 from gcal_sync.timeline import Timeline
15 from ical.iter import SortableItemValue
16 
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.core import HomeAssistant
19 from homeassistant.exceptions import HomeAssistantError
20 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
21 from homeassistant.util import dt as dt_util
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
26 # Maximum number of upcoming events to consider for state changes between
27 # coordinator updates.
28 MAX_UPCOMING_EVENTS = 20
29 
30 
31 def _truncate_timeline(timeline: Timeline, max_events: int) -> Timeline:
32  """Truncate the timeline to a maximum number of events.
33 
34  This is used to avoid repeated expansion of recurring events during
35  state machine updates.
36  """
37  upcoming = timeline.active_after(dt_util.now())
38  truncated = list(itertools.islice(upcoming, max_events))
39  return Timeline(
40  [
41  SortableItemValue(event.timespan_of(dt_util.get_default_time_zone()), event)
42  for event in truncated
43  ]
44  )
45 
46 
48  """Coordinator for calendar RPC calls that use an efficient sync."""
49 
50  config_entry: ConfigEntry
51 
52  def __init__(
53  self,
54  hass: HomeAssistant,
55  sync: CalendarEventSyncManager,
56  name: str,
57  ) -> None:
58  """Create the CalendarSyncUpdateCoordinator."""
59  super().__init__(
60  hass,
61  _LOGGER,
62  name=name,
63  update_interval=MIN_TIME_BETWEEN_UPDATES,
64  )
65  self.syncsync = sync
66  self._upcoming_timeline_upcoming_timeline: Timeline | None = None
67 
68  async def _async_update_data(self) -> Timeline:
69  """Fetch data from API endpoint."""
70  try:
71  await self.syncsync.run()
72  except ApiException as err:
73  raise UpdateFailed(f"Error communicating with API: {err}") from err
74 
75  timeline = await self.syncsync.store_service.async_get_timeline(
76  dt_util.get_default_time_zone()
77  )
78  self._upcoming_timeline_upcoming_timeline = _truncate_timeline(timeline, MAX_UPCOMING_EVENTS)
79  return timeline
80 
81  async def async_get_events(
82  self, start_date: datetime, end_date: datetime
83  ) -> Iterable[Event]:
84  """Get all events in a specific time frame."""
85  if not self.datadata:
86  raise HomeAssistantError(
87  "Unable to get events: Sync from server has not completed"
88  )
89  return self.datadata.overlapping(
90  start_date,
91  end_date,
92  )
93 
94  @property
95  def upcoming(self) -> Iterable[Event] | None:
96  """Return upcoming events if any."""
97  if self._upcoming_timeline_upcoming_timeline:
98  return self._upcoming_timeline_upcoming_timeline.active_after(dt_util.now())
99  return None
100 
101 
103  """Coordinator for calendar RPC calls.
104 
105  This sends a polling RPC, not using sync, as a workaround
106  for limitations in the calendar API for supporting search.
107  """
108 
109  config_entry: ConfigEntry
110 
111  def __init__(
112  self,
113  hass: HomeAssistant,
114  calendar_service: GoogleCalendarService,
115  name: str,
116  calendar_id: str,
117  search: str | None,
118  ) -> None:
119  """Create the CalendarQueryUpdateCoordinator."""
120  super().__init__(
121  hass,
122  _LOGGER,
123  name=name,
124  update_interval=MIN_TIME_BETWEEN_UPDATES,
125  )
126  self.calendar_servicecalendar_service = calendar_service
127  self.calendar_idcalendar_id = calendar_id
128  self._search_search = search
129 
130  async def async_get_events(
131  self, start_date: datetime, end_date: datetime
132  ) -> Iterable[Event]:
133  """Get all events in a specific time frame."""
134  request = ListEventsRequest(
135  calendar_id=self.calendar_idcalendar_id,
136  start_time=start_date,
137  end_time=end_date,
138  search=self._search_search,
139  )
140  result_items = []
141  try:
142  result = await self.calendar_servicecalendar_service.async_list_events(request)
143  async for result_page in result:
144  result_items.extend(result_page.items)
145  except ApiException as err:
146  self.async_set_update_errorasync_set_update_error(err)
147  raise HomeAssistantError(str(err)) from err
148  return result_items
149 
150  async def _async_update_data(self) -> list[Event]:
151  """Fetch data from API endpoint."""
152  request = ListEventsRequest(calendar_id=self.calendar_idcalendar_id, search=self._search_search)
153  try:
154  result = await self.calendar_servicecalendar_service.async_list_events(request)
155  except ApiException as err:
156  raise UpdateFailed(f"Error communicating with API: {err}") from err
157  return result.items
158 
159  @property
160  def upcoming(self) -> Iterable[Event] | None:
161  """Return the next upcoming event if any."""
162  return self.datadata
None __init__(self, HomeAssistant hass, GoogleCalendarService calendar_service, str name, str calendar_id, str|None search)
Definition: coordinator.py:118
Iterable[Event] async_get_events(self, datetime start_date, datetime end_date)
Definition: coordinator.py:132
None __init__(self, HomeAssistant hass, CalendarEventSyncManager sync, str name)
Definition: coordinator.py:57
Iterable[Event] async_get_events(self, datetime start_date, datetime end_date)
Definition: coordinator.py:83
Timeline _truncate_timeline(Timeline timeline, int max_events)
Definition: coordinator.py:31
int run(RuntimeConfig runtime_config)
Definition: runner.py:146