Skip to content

Commit 3100c60

Browse files
authored
Todoist update function + validation fixes (#2365)
## Description - **Summary of changes**: Describe the key changes in this PR and their purpose. - **Related issues**: Mention if this PR fixes or is connected to any issues. - **Motivation and context**: Explain the reason for the changes and the problem they solve. - **Environment or dependencies**: Specify any changes in dependencies or environment configurations required for this update. - **Impact on metrics**: (If applicable) Describe changes in any metrics or performance benchmarks. Fixes # (issue) --- ## Type of change Please check the options that are relevant: - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Model update (Addition or modification of models) - [ ] Other (please describe): --- ## Checklist - [ ] Adherence to standards: Code complies with Agno’s style guidelines and best practices. - [ ] Formatting and validation: You have run `./scripts/format.sh` and `./scripts/validate.sh` to ensure code is formatted and linted. - [ ] Self-review completed: A thorough review has been performed by the contributor(s). - [ ] Documentation: Docstrings and comments have been added or updated for any complex logic. - [ ] Examples and guides: Relevant cookbook examples have been included or updated (if applicable). - [ ] Tested in a clean environment: Changes have been tested in a clean environment to confirm expected behavior. - [ ] Tests (optional): Tests have been added or updated to cover any new or changed functionality. --- ## Additional Notes Include any deployment notes, performance implications, security considerations, or other relevant information (e.g., screenshots or logs if applicable).
1 parent 217816a commit 3100c60

File tree

2 files changed

+43
-112
lines changed

2 files changed

+43
-112
lines changed

libs/agno/agno/tools/todoist.py

+41-94
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
import os
3-
from typing import List, Optional
3+
from typing import Any, Dict, List, Optional
44

55
from agno.tools import Toolkit
66
from agno.utils.log import logger
@@ -61,6 +61,32 @@ def __init__(
6161
if get_projects:
6262
self.register(self.get_projects)
6363

64+
def _task_to_dict(self, task: Any) -> Dict[str, Any]:
65+
"""Convert a Todoist task to a dictionary with proper typing."""
66+
task_dict: Dict[str, Any] = {
67+
"id": task.id,
68+
"content": task.content,
69+
"description": task.description,
70+
"project_id": task.project_id,
71+
"section_id": task.section_id,
72+
"parent_id": task.parent_id,
73+
"order": task.order,
74+
"priority": task.priority,
75+
"url": task.url,
76+
"comment_count": task.comment_count,
77+
"creator_id": task.creator_id,
78+
"created_at": task.created_at,
79+
"labels": task.labels,
80+
}
81+
if task.due:
82+
task_dict["due"] = {
83+
"date": task.due.date,
84+
"string": task.due.string,
85+
"datetime": task.due.datetime,
86+
"timezone": task.due.timezone,
87+
}
88+
return task_dict
89+
6490
def create_task(
6591
self,
6692
content: str,
@@ -87,28 +113,7 @@ def create_task(
87113
content=content, project_id=project_id, due_string=due_string, priority=priority, labels=labels or []
88114
)
89115
# Convert task to a dictionary and handle the Due object
90-
task_dict = {
91-
"id": task.id,
92-
"content": task.content,
93-
"description": task.description,
94-
"project_id": task.project_id,
95-
"section_id": task.section_id,
96-
"parent_id": task.parent_id,
97-
"order": task.order,
98-
"priority": task.priority,
99-
"url": task.url,
100-
"comment_count": task.comment_count,
101-
"creator_id": task.creator_id,
102-
"created_at": task.created_at,
103-
"labels": task.labels,
104-
}
105-
if task.due:
106-
task_dict["due"] = {
107-
"date": task.due.date,
108-
"string": task.due.string,
109-
"datetime": task.due.datetime,
110-
"timezone": task.due.timezone,
111-
}
116+
task_dict = self._task_to_dict(task)
112117
return json.dumps(task_dict)
113118
except Exception as e:
114119
logger.error(f"Failed to create task: {str(e)}")
@@ -118,28 +123,7 @@ def get_task(self, task_id: str) -> str:
118123
"""Get a specific task by ID."""
119124
try:
120125
task = self.api.get_task(task_id)
121-
task_dict = {
122-
"id": task.id,
123-
"content": task.content,
124-
"description": task.description,
125-
"project_id": task.project_id,
126-
"section_id": task.section_id,
127-
"parent_id": task.parent_id,
128-
"order": task.order,
129-
"priority": task.priority,
130-
"url": task.url,
131-
"comment_count": task.comment_count,
132-
"creator_id": task.creator_id,
133-
"created_at": task.created_at,
134-
"labels": task.labels,
135-
}
136-
if task.due:
137-
task_dict["due"] = {
138-
"date": task.due.date,
139-
"string": task.due.string,
140-
"datetime": task.due.datetime,
141-
"timezone": task.due.timezone,
142-
}
126+
task_dict = self._task_to_dict(task)
143127
return json.dumps(task_dict)
144128
except Exception as e:
145129
logger.error(f"Failed to get task: {str(e)}")
@@ -152,38 +136,22 @@ def update_task(self, task_id: str, **kwargs) -> str:
152136
Args:
153137
task_id: The ID of the task to update
154138
**kwargs: Any task properties to update (content, due_string, priority, etc.)
139+
If a nested 'kwargs' dictionary is provided, its contents will be used instead.
155140
156141
Returns:
157-
str: JSON string containing the updated task
142+
str: JSON string containing success status
158143
"""
159144
try:
160-
task = self.api.update_task(task_id=task_id, **kwargs)
161-
task_dict = {
162-
"id": task.id,
163-
"content": task.content,
164-
"description": task.description,
165-
"project_id": task.project_id,
166-
"section_id": task.section_id,
167-
"parent_id": task.parent_id,
168-
"order": task.order,
169-
"priority": task.priority,
170-
"url": task.url,
171-
"comment_count": task.comment_count,
172-
"creator_id": task.creator_id,
173-
"created_at": task.created_at,
174-
"labels": task.labels,
175-
}
176-
if task.due:
177-
task_dict["due"] = {
178-
"date": task.due.date,
179-
"string": task.due.string,
180-
"datetime": task.due.datetime,
181-
"timezone": task.due.timezone,
182-
}
183-
return json.dumps(task_dict)
145+
# Check if there's a nested kwargs dictionary and use it if present
146+
if len(kwargs) == 1 and "kwargs" in kwargs and isinstance(kwargs["kwargs"], dict):
147+
kwargs = kwargs["kwargs"]
148+
149+
success = self.api.update_task(task_id=task_id, **kwargs)
150+
return json.dumps({"success": success})
184151
except Exception as e:
185-
logger.error(f"Failed to update task: {str(e)}")
186-
return json.dumps({"error": str(e)})
152+
error_msg = str(e)
153+
logger.error(f"Failed to update task: {error_msg}")
154+
return json.dumps({"error": error_msg})
187155

188156
def close_task(self, task_id: str) -> str:
189157
"""Mark a task as completed."""
@@ -209,28 +177,7 @@ def get_active_tasks(self) -> str:
209177
tasks = self.api.get_tasks()
210178
tasks_list = []
211179
for task in tasks:
212-
task_dict = {
213-
"id": task.id,
214-
"content": task.content,
215-
"description": task.description,
216-
"project_id": task.project_id,
217-
"section_id": task.section_id,
218-
"parent_id": task.parent_id,
219-
"order": task.order,
220-
"priority": task.priority,
221-
"url": task.url,
222-
"comment_count": task.comment_count,
223-
"creator_id": task.creator_id,
224-
"created_at": task.created_at,
225-
"labels": task.labels,
226-
}
227-
if task.due:
228-
task_dict["due"] = {
229-
"date": task.due.date,
230-
"string": task.due.string,
231-
"datetime": task.due.datetime,
232-
"timezone": task.due.timezone,
233-
}
180+
task_dict = self._task_to_dict(task)
234181
tasks_list.append(task_dict)
235182
return json.dumps(tasks_list)
236183
except Exception as e:

libs/agno/tests/unit/tools/test_todoist_tools.py

+2-18
Original file line numberDiff line numberDiff line change
@@ -130,28 +130,12 @@ def test_get_task_success(todoist_tools, mock_todoist_api):
130130

131131
def test_update_task_success(todoist_tools, mock_todoist_api):
132132
"""Test successful task update."""
133-
mock_task = Mock()
134-
mock_task.id = "123"
135-
mock_task.content = "Updated Task"
136-
mock_task.description = "Updated Description"
137-
mock_task.project_id = "project_1"
138-
mock_task.section_id = None
139-
mock_task.parent_id = None
140-
mock_task.order = 1
141-
mock_task.priority = 1
142-
mock_task.url = "https://todoist.com/task/123"
143-
mock_task.comment_count = 0
144-
mock_task.creator_id = "user_1"
145-
mock_task.created_at = "2024-01-01T10:00:00Z"
146-
mock_task.labels = []
147-
mock_task.due = None
148-
149-
mock_todoist_api.update_task.return_value = mock_task
133+
mock_todoist_api.update_task.return_value = True
150134

151135
result = todoist_tools.update_task("123", content="Updated Task")
152136
result_data = json.loads(result)
153137

154-
assert result_data["id"] == "123"
138+
assert result_data["success"] is True
155139
mock_todoist_api.update_task.assert_called_once_with(task_id="123", content="Updated Task")
156140

157141

0 commit comments

Comments
 (0)