mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add DataUpdateCoordinator to the Todoist integration (#89836)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
89a3c304c2
commit
9ccd43e5f1
@ -23,6 +23,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt
|
||||
|
||||
from .const import (
|
||||
@ -54,6 +55,7 @@ from .const import (
|
||||
START,
|
||||
SUMMARY,
|
||||
)
|
||||
from .coordinator import TodoistCoordinator
|
||||
from .types import CalData, CustomProject, ProjectData, TodoistEvent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -117,6 +119,8 @@ async def async_setup_platform(
|
||||
project_id_lookup = {}
|
||||
|
||||
api = TodoistAPIAsync(token)
|
||||
coordinator = TodoistCoordinator(hass, _LOGGER, SCAN_INTERVAL, api)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
# Setup devices:
|
||||
# Grab all projects.
|
||||
@ -131,7 +135,7 @@ async def async_setup_platform(
|
||||
# Project is an object, not a dict!
|
||||
# Because of that, we convert what we need to a dict.
|
||||
project_data: ProjectData = {CONF_NAME: project.name, CONF_ID: project.id}
|
||||
project_devices.append(TodoistProjectEntity(project_data, labels, api))
|
||||
project_devices.append(TodoistProjectEntity(coordinator, project_data, labels))
|
||||
# Cache the names so we can easily look up name->ID.
|
||||
project_id_lookup[project.name.lower()] = project.id
|
||||
|
||||
@ -157,9 +161,9 @@ async def async_setup_platform(
|
||||
# Create the custom project and add it to the devices array.
|
||||
project_devices.append(
|
||||
TodoistProjectEntity(
|
||||
coordinator,
|
||||
{"id": None, "name": extra_project["name"]},
|
||||
labels,
|
||||
api,
|
||||
due_date_days=project_due_date,
|
||||
whitelisted_labels=project_label_filter,
|
||||
whitelisted_projects=project_id_filter,
|
||||
@ -267,23 +271,24 @@ async def async_setup_platform(
|
||||
)
|
||||
|
||||
|
||||
class TodoistProjectEntity(CalendarEntity):
|
||||
class TodoistProjectEntity(CoordinatorEntity[TodoistCoordinator], CalendarEntity):
|
||||
"""A device for getting the next Task from a Todoist Project."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TodoistCoordinator,
|
||||
data: ProjectData,
|
||||
labels: list[Label],
|
||||
api: TodoistAPIAsync,
|
||||
due_date_days: int | None = None,
|
||||
whitelisted_labels: list[str] | None = None,
|
||||
whitelisted_projects: list[str] | None = None,
|
||||
) -> None:
|
||||
"""Create the Todoist Calendar Entity."""
|
||||
super().__init__(coordinator=coordinator)
|
||||
self.data = TodoistProjectData(
|
||||
data,
|
||||
labels,
|
||||
api,
|
||||
coordinator,
|
||||
due_date_days=due_date_days,
|
||||
whitelisted_labels=whitelisted_labels,
|
||||
whitelisted_projects=whitelisted_projects,
|
||||
@ -306,6 +311,7 @@ class TodoistProjectEntity(CalendarEntity):
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update all Todoist Calendars."""
|
||||
await super().async_update()
|
||||
await self.data.async_update()
|
||||
# Set Todoist-specific data that can't easily be grabbed
|
||||
self._cal_data["all_tasks"] = [
|
||||
@ -373,7 +379,7 @@ class TodoistProjectData:
|
||||
self,
|
||||
project_data: ProjectData,
|
||||
labels: list[Label],
|
||||
api: TodoistAPIAsync,
|
||||
coordinator: TodoistCoordinator,
|
||||
due_date_days: int | None = None,
|
||||
whitelisted_labels: list[str] | None = None,
|
||||
whitelisted_projects: list[str] | None = None,
|
||||
@ -381,7 +387,7 @@ class TodoistProjectData:
|
||||
"""Initialize a Todoist Project."""
|
||||
self.event: TodoistEvent | None = None
|
||||
|
||||
self._api = api
|
||||
self._coordinator = coordinator
|
||||
self._name = project_data[CONF_NAME]
|
||||
# If no ID is defined, fetch all tasks.
|
||||
self._id = project_data.get(CONF_ID)
|
||||
@ -569,8 +575,8 @@ class TodoistProjectData:
|
||||
self, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all tasks in a specific time frame."""
|
||||
tasks = self._coordinator.data
|
||||
if self._id is None:
|
||||
tasks = await self._api.get_tasks()
|
||||
project_task_data = [
|
||||
task
|
||||
for task in tasks
|
||||
@ -578,7 +584,7 @@ class TodoistProjectData:
|
||||
or task.project_id in self._project_id_whitelist
|
||||
]
|
||||
else:
|
||||
project_task_data = await self._api.get_tasks(project_id=self._id)
|
||||
project_task_data = [task for task in tasks if task.project_id == self._id]
|
||||
|
||||
events = []
|
||||
for task in project_task_data:
|
||||
@ -607,8 +613,8 @@ class TodoistProjectData:
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
tasks = self._coordinator.data
|
||||
if self._id is None:
|
||||
tasks = await self._api.get_tasks()
|
||||
project_task_data = [
|
||||
task
|
||||
for task in tasks
|
||||
@ -616,7 +622,7 @@ class TodoistProjectData:
|
||||
or task.project_id in self._project_id_whitelist
|
||||
]
|
||||
else:
|
||||
project_task_data = await self._api.get_tasks(project_id=self._id)
|
||||
project_task_data = [task for task in tasks if task.project_id == self._id]
|
||||
|
||||
# If we have no data, we can just return right away.
|
||||
if not project_task_data:
|
||||
|
31
homeassistant/components/todoist/coordinator.py
Normal file
31
homeassistant/components/todoist/coordinator.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""DataUpdateCoordinator for the Todoist component."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from todoist_api_python.api_async import TodoistAPIAsync
|
||||
from todoist_api_python.models import Task
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
|
||||
class TodoistCoordinator(DataUpdateCoordinator[list[Task]]):
|
||||
"""Coordinator for updating task data from Todoist."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
logger: logging.Logger,
|
||||
update_interval: timedelta,
|
||||
api: TodoistAPIAsync,
|
||||
) -> None:
|
||||
"""Initialize the Todoist coordinator."""
|
||||
super().__init__(hass, logger, name="Todoist", update_interval=update_interval)
|
||||
self.api = api
|
||||
|
||||
async def _async_update_data(self) -> list[Task]:
|
||||
"""Fetch tasks from the Todoist API."""
|
||||
try:
|
||||
return await self.api.get_tasks()
|
||||
except Exception as err:
|
||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
@ -132,6 +132,30 @@ async def test_update_entity_for_custom_project_with_labels_on(
|
||||
assert state.state == "on"
|
||||
|
||||
|
||||
@patch("homeassistant.components.todoist.calendar.TodoistAPIAsync")
|
||||
async def test_failed_coordinator_update(todoist_api, hass: HomeAssistant, api) -> None:
|
||||
"""Test a failed data coordinator update is handled correctly."""
|
||||
api.get_tasks.side_effect = Exception("API error")
|
||||
todoist_api.return_value = api
|
||||
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"calendar",
|
||||
{
|
||||
"calendar": {
|
||||
"platform": DOMAIN,
|
||||
CONF_TOKEN: "token",
|
||||
"custom_projects": [{"name": "All projects", "labels": ["Label1"]}],
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await async_update_entity(hass, "calendar.all_projects")
|
||||
state = hass.states.get("calendar.all_projects")
|
||||
assert state is None
|
||||
|
||||
|
||||
@patch("homeassistant.components.todoist.calendar.TodoistAPIAsync")
|
||||
async def test_calendar_custom_project_unique_id(
|
||||
todoist_api, hass: HomeAssistant, api, entity_registry: er.EntityRegistry
|
||||
|
Loading…
x
Reference in New Issue
Block a user