mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add more types to the todoist integration (#84210)
* Add more types to the todoist integration. * Update tests. * Update homeassistant/components/todoist/calendar.py Pass f-string directly to strftime. Co-authored-by: Allen Porter <allen.porter@gmail.com> * Add back mistakenly removed local var. Co-authored-by: Allen Porter <allen.porter@gmail.com>
This commit is contained in:
parent
c212e317c3
commit
3405fa60ec
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from todoist.api import TodoistAPI
|
||||
import voluptuous as vol
|
||||
@ -57,7 +58,7 @@ from .const import (
|
||||
SUMMARY,
|
||||
TASKS,
|
||||
)
|
||||
from .types import DueDate
|
||||
from .types import CalData, CustomProject, DueDate, ProjectData, TodoistEvent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -137,8 +138,8 @@ def setup_platform(
|
||||
for project in projects:
|
||||
# Project is an object, not a dict!
|
||||
# Because of that, we convert what we need to a dict.
|
||||
project_data = {CONF_NAME: project[NAME], CONF_ID: project[ID]}
|
||||
project_devices.append(TodoistProjectEntity(hass, project_data, labels, api))
|
||||
project_data: ProjectData = {CONF_NAME: project[NAME], CONF_ID: project[ID]}
|
||||
project_devices.append(TodoistProjectEntity(project_data, labels, api))
|
||||
# Cache the names so we can easily look up name->ID.
|
||||
project_id_lookup[project[NAME].lower()] = project[ID]
|
||||
|
||||
@ -150,7 +151,7 @@ def setup_platform(
|
||||
collaborator_id_lookup[collaborator[FULL_NAME].lower()] = collaborator[ID]
|
||||
|
||||
# Check config for more projects.
|
||||
extra_projects = config[CONF_EXTRA_PROJECTS]
|
||||
extra_projects: list[CustomProject] = config[CONF_EXTRA_PROJECTS]
|
||||
for project in extra_projects:
|
||||
# Special filter: By date
|
||||
project_due_date = project.get(CONF_PROJECT_DUE_DATE)
|
||||
@ -169,7 +170,6 @@ def setup_platform(
|
||||
# Create the custom project and add it to the devices array.
|
||||
project_devices.append(
|
||||
TodoistProjectEntity(
|
||||
hass,
|
||||
project,
|
||||
labels,
|
||||
api,
|
||||
@ -277,14 +277,13 @@ class TodoistProjectEntity(CalendarEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
data,
|
||||
labels,
|
||||
token,
|
||||
due_date_days=None,
|
||||
whitelisted_labels=None,
|
||||
whitelisted_projects=None,
|
||||
):
|
||||
data: ProjectData,
|
||||
labels: list[str],
|
||||
token: TodoistAPI,
|
||||
due_date_days: int | None = None,
|
||||
whitelisted_labels: list[str] | None = None,
|
||||
whitelisted_projects: list[int] | None = None,
|
||||
) -> None:
|
||||
"""Create the Todoist Calendar Entity."""
|
||||
self.data = TodoistProjectData(
|
||||
data,
|
||||
@ -294,17 +293,19 @@ class TodoistProjectEntity(CalendarEntity):
|
||||
whitelisted_labels,
|
||||
whitelisted_projects,
|
||||
)
|
||||
self._cal_data = {}
|
||||
self._cal_data: CalData = {}
|
||||
self._name = data[CONF_NAME]
|
||||
self._attr_unique_id = data.get(CONF_ID)
|
||||
self._attr_unique_id = (
|
||||
str(data[CONF_ID]) if data.get(CONF_ID) is not None else None
|
||||
)
|
||||
|
||||
@property
|
||||
def event(self) -> CalendarEvent:
|
||||
def event(self) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
return self.data.calendar_event
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@ -326,7 +327,7 @@ class TodoistProjectEntity(CalendarEntity):
|
||||
return await self.data.async_get_events(hass, start_date, end_date)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the device state attributes."""
|
||||
if self.data.event is None:
|
||||
# No tasks, we don't REALLY need to show anything.
|
||||
@ -376,13 +377,13 @@ class TodoistProjectData:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_data,
|
||||
labels,
|
||||
api,
|
||||
due_date_days=None,
|
||||
whitelisted_labels=None,
|
||||
whitelisted_projects=None,
|
||||
):
|
||||
project_data: ProjectData,
|
||||
labels: list[str],
|
||||
api: TodoistAPI,
|
||||
due_date_days: int | None = None,
|
||||
whitelisted_labels: list[str] | None = None,
|
||||
whitelisted_projects: list[int] | None = None,
|
||||
) -> None:
|
||||
"""Initialize a Todoist Project."""
|
||||
self.event = None
|
||||
|
||||
@ -395,26 +396,23 @@ class TodoistProjectData:
|
||||
self._labels = labels
|
||||
# Not tracked: order, indent, comment_count.
|
||||
|
||||
self.all_project_tasks = []
|
||||
self.all_project_tasks: list[TodoistEvent] = []
|
||||
|
||||
# The days a task can be due (for making lists of everything
|
||||
# due today, or everything due in the next week, for example).
|
||||
self._due_date_days: timedelta | None = None
|
||||
if due_date_days is not None:
|
||||
self._due_date_days = timedelta(days=due_date_days)
|
||||
else:
|
||||
self._due_date_days = None
|
||||
|
||||
# Only tasks with one of these labels will be included.
|
||||
self._label_whitelist: list[str] = []
|
||||
if whitelisted_labels is not None:
|
||||
self._label_whitelist = whitelisted_labels
|
||||
else:
|
||||
self._label_whitelist = []
|
||||
|
||||
# This project includes only projects with these names.
|
||||
self._project_id_whitelist: list[int] = []
|
||||
if whitelisted_projects is not None:
|
||||
self._project_id_whitelist = whitelisted_projects
|
||||
else:
|
||||
self._project_id_whitelist = []
|
||||
|
||||
@property
|
||||
def calendar_event(self) -> CalendarEvent | None:
|
||||
@ -502,7 +500,7 @@ class TodoistProjectData:
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def select_best_task(project_tasks):
|
||||
def select_best_task(project_tasks: list[TodoistEvent]) -> TodoistEvent:
|
||||
"""
|
||||
Search through a list of events for the "best" event to select.
|
||||
|
||||
@ -562,14 +560,16 @@ class TodoistProjectData:
|
||||
continue
|
||||
|
||||
if proposed_event[PRIORITY] == event[PRIORITY] and (
|
||||
proposed_event[END] < event[END]
|
||||
event[END] is not None and proposed_event[END] < event[END]
|
||||
):
|
||||
event = proposed_event
|
||||
continue
|
||||
|
||||
return event
|
||||
|
||||
async def async_get_events(self, hass, start_date, end_date):
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all tasks in a specific time frame."""
|
||||
if self._id is None:
|
||||
project_task_data = [
|
||||
@ -594,13 +594,14 @@ class TodoistProjectData:
|
||||
)
|
||||
if not due_date:
|
||||
continue
|
||||
midnight = dt.as_utc(
|
||||
dt.parse_datetime(
|
||||
due_date.strftime("%Y-%m-%d")
|
||||
+ "T00:00:00"
|
||||
+ self._api.state["user"]["tz_info"]["gmt_string"]
|
||||
)
|
||||
gmt_string = self._api.state["user"]["tz_info"]["gmt_string"]
|
||||
local_midnight = dt.parse_datetime(
|
||||
due_date.strftime(f"%Y-%m-%dT00:00:00{gmt_string}")
|
||||
)
|
||||
if local_midnight is not None:
|
||||
midnight = dt.as_utc(local_midnight)
|
||||
else:
|
||||
midnight = due_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
if start_date < due_date < end_date:
|
||||
due_date_value: datetime | date = due_date
|
||||
|
@ -1,35 +1,37 @@
|
||||
"""Constants for the Todoist component."""
|
||||
CONF_EXTRA_PROJECTS = "custom_projects"
|
||||
CONF_PROJECT_DUE_DATE = "due_date_days"
|
||||
CONF_PROJECT_LABEL_WHITELIST = "labels"
|
||||
CONF_PROJECT_WHITELIST = "include_projects"
|
||||
from typing import Final
|
||||
|
||||
CONF_EXTRA_PROJECTS: Final = "custom_projects"
|
||||
CONF_PROJECT_DUE_DATE: Final = "due_date_days"
|
||||
CONF_PROJECT_LABEL_WHITELIST: Final = "labels"
|
||||
CONF_PROJECT_WHITELIST: Final = "include_projects"
|
||||
|
||||
# Calendar Platform: Does this calendar event last all day?
|
||||
ALL_DAY = "all_day"
|
||||
ALL_DAY: Final = "all_day"
|
||||
# Attribute: All tasks in this project
|
||||
ALL_TASKS = "all_tasks"
|
||||
ALL_TASKS: Final = "all_tasks"
|
||||
# Todoist API: "Completed" flag -- 1 if complete, else 0
|
||||
CHECKED = "checked"
|
||||
CHECKED: Final = "checked"
|
||||
# Attribute: Is this task complete?
|
||||
COMPLETED = "completed"
|
||||
COMPLETED: Final = "completed"
|
||||
# Todoist API: What is this task about?
|
||||
# Service Call: What is this task about?
|
||||
CONTENT = "content"
|
||||
CONTENT: Final = "content"
|
||||
# Calendar Platform: Get a calendar event's description
|
||||
DESCRIPTION = "description"
|
||||
DESCRIPTION: Final = "description"
|
||||
# Calendar Platform: Used in the '_get_date()' method
|
||||
DATETIME = "dateTime"
|
||||
DUE = "due"
|
||||
DATETIME: Final = "dateTime"
|
||||
DUE: Final = "due"
|
||||
# Service Call: When is this task due (in natural language)?
|
||||
DUE_DATE_STRING = "due_date_string"
|
||||
DUE_DATE_STRING: Final = "due_date_string"
|
||||
# Service Call: The language of DUE_DATE_STRING
|
||||
DUE_DATE_LANG = "due_date_lang"
|
||||
DUE_DATE_LANG: Final = "due_date_lang"
|
||||
# Service Call: When should user be reminded of this task (in natural language)?
|
||||
REMINDER_DATE_STRING = "reminder_date_string"
|
||||
REMINDER_DATE_STRING: Final = "reminder_date_string"
|
||||
# Service Call: The language of REMINDER_DATE_STRING
|
||||
REMINDER_DATE_LANG = "reminder_date_lang"
|
||||
REMINDER_DATE_LANG: Final = "reminder_date_lang"
|
||||
# Service Call: The available options of DUE_DATE_LANG
|
||||
DUE_DATE_VALID_LANGS = [
|
||||
DUE_DATE_VALID_LANGS: Final = [
|
||||
"en",
|
||||
"da",
|
||||
"pl",
|
||||
@ -47,45 +49,45 @@ DUE_DATE_VALID_LANGS = [
|
||||
]
|
||||
# Attribute: When is this task due?
|
||||
# Service Call: When is this task due?
|
||||
DUE_DATE = "due_date"
|
||||
DUE_DATE: Final = "due_date"
|
||||
# Service Call: When should user be reminded of this task?
|
||||
REMINDER_DATE = "reminder_date"
|
||||
REMINDER_DATE: Final = "reminder_date"
|
||||
# Attribute: Is this task due today?
|
||||
DUE_TODAY = "due_today"
|
||||
DUE_TODAY: Final = "due_today"
|
||||
# Calendar Platform: When a calendar event ends
|
||||
END = "end"
|
||||
END: Final = "end"
|
||||
# Todoist API: Look up a Project/Label/Task ID
|
||||
ID = "id"
|
||||
ID: Final = "id"
|
||||
# Todoist API: Fetch all labels
|
||||
# Service Call: What are the labels attached to this task?
|
||||
LABELS = "labels"
|
||||
LABELS: Final = "labels"
|
||||
# Todoist API: "Name" value
|
||||
NAME = "name"
|
||||
NAME: Final = "name"
|
||||
# Todoist API: "Full Name" value
|
||||
FULL_NAME = "full_name"
|
||||
FULL_NAME: Final = "full_name"
|
||||
# Attribute: Is this task overdue?
|
||||
OVERDUE = "overdue"
|
||||
OVERDUE: Final = "overdue"
|
||||
# Attribute: What is this task's priority?
|
||||
# Todoist API: Get a task's priority
|
||||
# Service Call: What is this task's priority?
|
||||
PRIORITY = "priority"
|
||||
PRIORITY: Final = "priority"
|
||||
# Todoist API: Look up the Project ID a Task belongs to
|
||||
PROJECT_ID = "project_id"
|
||||
PROJECT_ID: Final = "project_id"
|
||||
# Service Call: What Project do you want a Task added to?
|
||||
PROJECT_NAME = "project"
|
||||
PROJECT_NAME: Final = "project"
|
||||
# Todoist API: Fetch all Projects
|
||||
PROJECTS = "projects"
|
||||
PROJECTS: Final = "projects"
|
||||
# Calendar Platform: When does a calendar event start?
|
||||
START = "start"
|
||||
START: Final = "start"
|
||||
# Calendar Platform: What is the next calendar event about?
|
||||
SUMMARY = "summary"
|
||||
SUMMARY: Final = "summary"
|
||||
# Todoist API: Fetch all Tasks
|
||||
TASKS = "items"
|
||||
TASKS: Final = "items"
|
||||
# Todoist API: "responsible" for a Task
|
||||
ASSIGNEE = "assignee"
|
||||
ASSIGNEE: Final = "assignee"
|
||||
# Todoist API: Collaborators in shared projects
|
||||
COLLABORATORS = "collaborators"
|
||||
COLLABORATORS: Final = "collaborators"
|
||||
|
||||
DOMAIN = "todoist"
|
||||
DOMAIN: Final = "todoist"
|
||||
|
||||
SERVICE_NEW_TASK = "new_task"
|
||||
SERVICE_NEW_TASK: Final = "new_task"
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Types for the Todoist component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
@ -12,3 +13,40 @@ class DueDate(TypedDict):
|
||||
lang: str
|
||||
string: str
|
||||
timezone: str | None
|
||||
|
||||
|
||||
class ProjectData(TypedDict):
|
||||
"""Dict representing project data."""
|
||||
|
||||
name: str
|
||||
id: int | None
|
||||
|
||||
|
||||
class CustomProject(TypedDict):
|
||||
"""Dict representing a custom project."""
|
||||
|
||||
name: str
|
||||
due_date_days: int | None
|
||||
include_projects: list[str] | None
|
||||
labels: list[str] | None
|
||||
|
||||
|
||||
class CalData(TypedDict, total=False):
|
||||
"""Dict representing calendar data in todoist."""
|
||||
|
||||
all_tasks: list[str]
|
||||
|
||||
|
||||
class TodoistEvent(TypedDict):
|
||||
"""Dict representing a todoist event."""
|
||||
|
||||
all_day: bool
|
||||
completed: bool
|
||||
description: str
|
||||
due_today: bool
|
||||
end: datetime | None
|
||||
labels: list[str]
|
||||
overdue: bool
|
||||
priority: int
|
||||
start: datetime
|
||||
summary: str
|
||||
|
@ -57,7 +57,7 @@ def mock_state() -> dict[str, Any]:
|
||||
return {
|
||||
"collaborators": [],
|
||||
"labels": [{"name": "label1", "id": 1}],
|
||||
"projects": [{"id": 12345, "name": "Name"}],
|
||||
"projects": [{"id": "12345", "name": "Name"}],
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ async def test_calendar_entity_unique_id(todoist_api, hass, state):
|
||||
|
||||
registry = entity_registry.async_get(hass)
|
||||
entity = registry.async_get("calendar.name")
|
||||
assert 12345 == entity.unique_id
|
||||
assert "12345" == entity.unique_id
|
||||
|
||||
|
||||
@patch("homeassistant.components.todoist.calendar.TodoistAPI")
|
||||
|
Loading…
x
Reference in New Issue
Block a user