Skip to content

Commit 726025d

Browse files
Merge pull request #198 from radiant-tangent/main
2 parents a424bf2 + ba851ca commit 726025d

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed

tests/test_api_tasks.py

+38
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,44 @@ async def test_uncomplete_task(
436436
assert response is True
437437

438438

439+
@pytest.mark.asyncio
440+
async def test_move_task(
441+
todoist_api: TodoistAPI,
442+
todoist_api_async: TodoistAPIAsync,
443+
requests_mock: responses.RequestsMock,
444+
) -> None:
445+
task_id = "6X7rM8997g3RQmvh"
446+
endpoint = f"{DEFAULT_API_URL}/tasks/{task_id}/move"
447+
448+
requests_mock.add(
449+
method=responses.POST,
450+
url=endpoint,
451+
status=204,
452+
match=[auth_matcher()],
453+
)
454+
455+
response = todoist_api.move_task(task_id, project_id="123")
456+
457+
assert len(requests_mock.calls) == 1
458+
assert response is True
459+
460+
response = await todoist_api_async.move_task(task_id, section_id="456")
461+
462+
assert len(requests_mock.calls) == 2
463+
assert response is True
464+
465+
response = await todoist_api_async.move_task(task_id, parent_id="789")
466+
467+
assert len(requests_mock.calls) == 3
468+
assert response is True
469+
470+
with pytest.raises(
471+
ValueError,
472+
match="Either `project_id`, `section_id`, or `parent_id` must be provided.",
473+
):
474+
response = await todoist_api_async.move_task(task_id)
475+
476+
439477
@pytest.mark.asyncio
440478
async def test_delete_task(
441479
todoist_api: TodoistAPI,

todoist_api_python/api.py

+39
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,45 @@ def uncomplete_task(self, task_id: str) -> bool:
473473
endpoint = get_api_url(f"{TASKS_PATH}/{task_id}/reopen")
474474
return post(self._session, endpoint, self._token)
475475

476+
def move_task(
477+
self,
478+
task_id: str,
479+
project_id: str | None = None,
480+
section_id: str | None = None,
481+
parent_id: str | None = None,
482+
) -> bool:
483+
"""
484+
Move a task to a different project, section, or parent task.
485+
486+
`project_id` takes predence, followed by
487+
`section_id` (which also updates `project_id`),
488+
and then `parent_id` (which also updates `section_id` and `project_id`).
489+
490+
:param task_id: The ID of the task to move.
491+
:param project_id: The ID of the project to move the task to.
492+
:param section_id: The ID of the section to move the task to.
493+
:param parent_id: The ID of the parent to move the task to.
494+
:return: True if the task was moved successfully,
495+
False otherwise (possibly raise `HTTPError` instead).
496+
:raises requests.exceptions.HTTPError: If the API request fails.
497+
:raises ValueError: If neither `project_id`, `section_id`,
498+
nor `parent_id` is provided.
499+
"""
500+
if project_id is None and section_id is None and parent_id is None:
501+
raise ValueError(
502+
"Either `project_id`, `section_id`, or `parent_id` must be provided."
503+
)
504+
505+
data: dict[str, Any] = {}
506+
if project_id is not None:
507+
data["project_id"] = project_id
508+
if section_id is not None:
509+
data["section_id"] = section_id
510+
if parent_id is not None:
511+
data["parent_id"] = parent_id
512+
endpoint = get_api_url(f"{TASKS_PATH}/{task_id}/move")
513+
return post(self._session, endpoint, self._token, data=data)
514+
476515
def delete_task(self, task_id: str) -> bool:
477516
"""
478517
Delete a task.

todoist_api_python/api_async.py

+33
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,39 @@ async def uncomplete_task(self, task_id: str) -> bool:
342342
"""
343343
return await run_async(lambda: self._api.uncomplete_task(task_id))
344344

345+
async def move_task(
346+
self,
347+
task_id: str,
348+
project_id: str | None = None,
349+
section_id: str | None = None,
350+
parent_id: str | None = None,
351+
) -> bool:
352+
"""
353+
Move a task to a different project, section, or parent task.
354+
355+
`project_id` takes predence, followed by
356+
`section_id` (which also updates `project_id`),
357+
and then `parent_id` (which also updates `section_id` and `project_id`).
358+
359+
:param task_id: The ID of the task to move.
360+
:param project_id: The ID of the project to move the task to.
361+
:param section_id: The ID of the section to move the task to.
362+
:param parent_id: The ID of the parent to move the task to.
363+
:return: True if the task was moved successfully,
364+
False otherwise (possibly raise `HTTPError` instead).
365+
:raises requests.exceptions.HTTPError: If the API request fails.
366+
:raises ValueError: If neither `project_id`, `section_id`,
367+
nor `parent_id` is provided.
368+
"""
369+
return await run_async(
370+
lambda: self._api.move_task(
371+
task_id,
372+
project_id=project_id,
373+
section_id=section_id,
374+
parent_id=parent_id,
375+
)
376+
)
377+
345378
async def delete_task(self, task_id: str) -> bool:
346379
"""
347380
Delete a task.

0 commit comments

Comments
 (0)