Home Assistant Unofficial Reference 2024.12.1
todo.py
Go to the documentation of this file.
1 """Google Tasks todo platform."""
2 
3 from __future__ import annotations
4 
5 from datetime import UTC, date, datetime, timedelta
6 from typing import Any, cast
7 
9  TodoItem,
10  TodoItemStatus,
11  TodoListEntity,
12  TodoListEntityFeature,
13 )
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.core import HomeAssistant
16 from homeassistant.helpers.entity_platform import AddEntitiesCallback
17 from homeassistant.helpers.update_coordinator import CoordinatorEntity
18 from homeassistant.util import dt as dt_util
19 
20 from .api import AsyncConfigEntryAuth
21 from .const import DOMAIN
22 from .coordinator import TaskUpdateCoordinator
23 
24 SCAN_INTERVAL = timedelta(minutes=15)
25 
26 TODO_STATUS_MAP = {
27  "needsAction": TodoItemStatus.NEEDS_ACTION,
28  "completed": TodoItemStatus.COMPLETED,
29 }
30 TODO_STATUS_MAP_INV = {v: k for k, v in TODO_STATUS_MAP.items()}
31 
32 
33 def _convert_todo_item(item: TodoItem) -> dict[str, str | None]:
34  """Convert TodoItem dataclass items to dictionary of attributes the tasks API."""
35  result: dict[str, str | None] = {}
36  result["title"] = item.summary
37  if item.status is not None:
38  result["status"] = TODO_STATUS_MAP_INV[item.status]
39  else:
40  result["status"] = TodoItemStatus.NEEDS_ACTION
41  if (due := item.due) is not None:
42  # due API field is a timestamp string, but with only date resolution.
43  # The time portion of the date is always discarded by the API, so we
44  # always set to UTC.
45  result["due"] = dt_util.start_of_local_day(due).replace(tzinfo=UTC).isoformat()
46  else:
47  result["due"] = None
48  result["notes"] = item.description
49  return result
50 
51 
52 def _convert_api_item(item: dict[str, str]) -> TodoItem:
53  """Convert tasks API items into a TodoItem."""
54  due: date | None = None
55  if (due_str := item.get("due")) is not None:
56  # Due dates are returned always in UTC so we only need to
57  # parse the date portion which will be interpreted as a a local date.
58  due = datetime.fromisoformat(due_str).date()
59  return TodoItem(
60  summary=item["title"],
61  uid=item["id"],
62  status=TODO_STATUS_MAP.get(
63  item.get("status", ""),
64  TodoItemStatus.NEEDS_ACTION,
65  ),
66  due=due,
67  description=item.get("notes"),
68  )
69 
70 
72  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
73 ) -> None:
74  """Set up the Google Tasks todo platform."""
75  api: AsyncConfigEntryAuth = hass.data[DOMAIN][entry.entry_id]
76  task_lists = await api.list_task_lists()
78  (
80  TaskUpdateCoordinator(hass, api, task_list["id"]),
81  task_list["title"],
82  entry.entry_id,
83  task_list["id"],
84  )
85  for task_list in task_lists
86  ),
87  True,
88  )
89 
90 
92  CoordinatorEntity[TaskUpdateCoordinator], TodoListEntity
93 ):
94  """A To-do List representation of the Shopping List."""
95 
96  _attr_has_entity_name = True
97  _attr_supported_features = (
98  TodoListEntityFeature.CREATE_TODO_ITEM
99  | TodoListEntityFeature.UPDATE_TODO_ITEM
100  | TodoListEntityFeature.DELETE_TODO_ITEM
101  | TodoListEntityFeature.MOVE_TODO_ITEM
102  | TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
103  | TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
104  )
105 
106  def __init__(
107  self,
108  coordinator: TaskUpdateCoordinator,
109  name: str,
110  config_entry_id: str,
111  task_list_id: str,
112  ) -> None:
113  """Initialize GoogleTaskTodoListEntity."""
114  super().__init__(coordinator)
115  self._attr_name_attr_name = name.capitalize()
116  self._attr_unique_id_attr_unique_id = f"{config_entry_id}-{task_list_id}"
117  self._task_list_id_task_list_id_task_list_id = task_list_id
118 
119  @property
120  def todo_items(self) -> list[TodoItem] | None:
121  """Get the current set of To-do items."""
122  if self.coordinator.data is None:
123  return None
124  return [_convert_api_item(item) for item in _order_tasks(self.coordinator.data)]
125 
126  async def async_create_todo_item(self, item: TodoItem) -> None:
127  """Add an item to the To-do list."""
128  await self.coordinator.api.insert(
129  self._task_list_id_task_list_id_task_list_id,
130  task=_convert_todo_item(item),
131  )
132  await self.coordinator.async_refresh()
133 
134  async def async_update_todo_item(self, item: TodoItem) -> None:
135  """Update a To-do item."""
136  uid: str = cast(str, item.uid)
137  await self.coordinator.api.patch(
138  self._task_list_id_task_list_id_task_list_id,
139  uid,
140  task=_convert_todo_item(item),
141  )
142  await self.coordinator.async_refresh()
143 
144  async def async_delete_todo_items(self, uids: list[str]) -> None:
145  """Delete To-do items."""
146  await self.coordinator.api.delete(self._task_list_id_task_list_id_task_list_id, uids)
147  await self.coordinator.async_refresh()
148 
150  self, uid: str, previous_uid: str | None = None
151  ) -> None:
152  """Re-order a To-do item."""
153  await self.coordinator.api.move(self._task_list_id_task_list_id_task_list_id, uid, previous=previous_uid)
154  await self.coordinator.async_refresh()
155 
156 
157 def _order_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
158  """Order the task items response.
159 
160  All tasks have an order amongst their siblings based on position.
161 
162  Home Assistant To-do items do not support the Google Task parent/sibling
163  relationships and the desired behavior is for them to be filtered.
164  """
165  parents = [task for task in tasks if task.get("parent") is None]
166  parents.sort(key=lambda task: task["position"])
167  return parents
None async_move_todo_item(self, str uid, str|None previous_uid=None)
Definition: todo.py:151
None __init__(self, TaskUpdateCoordinator coordinator, str name, str config_entry_id, str task_list_id)
Definition: todo.py:112
TodoItem _convert_api_item(dict[str, str] item)
Definition: todo.py:52
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: todo.py:73
list[dict[str, Any]] _order_tasks(list[dict[str, Any]] tasks)
Definition: todo.py:157
dict[str, str|None] _convert_todo_item(TodoItem item)
Definition: todo.py:33