todoist-cli-tool 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- todoist_cli_tool-0.1.0/PKG-INFO +15 -0
- todoist_cli_tool-0.1.0/README.md +1 -0
- todoist_cli_tool-0.1.0/pyproject.toml +18 -0
- todoist_cli_tool-0.1.0/setup.cfg +4 -0
- todoist_cli_tool-0.1.0/src/__init__.py +0 -0
- todoist_cli_tool-0.1.0/src/exception.py +27 -0
- todoist_cli_tool-0.1.0/src/models.py +46 -0
- todoist_cli_tool-0.1.0/src/todoist.py +116 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/PKG-INFO +15 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/SOURCES.txt +12 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/dependency_links.txt +1 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/entry_points.txt +2 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/requires.txt +7 -0
- todoist_cli_tool-0.1.0/src/todoist_cli_tool.egg-info/top_level.txt +4 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: todoist-cli-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Todoist CLI - Manage your tasks from the command line
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pydantic>=2.12.3
|
|
8
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
9
|
+
Requires-Dist: questionary>=2.1.1
|
|
10
|
+
Requires-Dist: requests>=2.32.4
|
|
11
|
+
Requires-Dist: rich>=14.0.0
|
|
12
|
+
Requires-Dist: textual>=3.3.0
|
|
13
|
+
Requires-Dist: typer>=0.20.0
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "todoist-cli-tool"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Todoist CLI - Manage your tasks from the command line"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pydantic>=2.12.3",
|
|
9
|
+
"python-dotenv>=1.1.1",
|
|
10
|
+
"questionary>=2.1.1",
|
|
11
|
+
"requests>=2.32.4",
|
|
12
|
+
"rich>=14.0.0",
|
|
13
|
+
"textual>=3.3.0",
|
|
14
|
+
"typer>=0.20.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
todoist = "main:app"
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
class TodoistException(Exception):
|
|
3
|
+
def __init__(
|
|
4
|
+
self,
|
|
5
|
+
message: str,
|
|
6
|
+
code: Optional[str | int] = None,
|
|
7
|
+
details: Optional[Dict[str, Any]] = None
|
|
8
|
+
) -> None:
|
|
9
|
+
# Call the base Exception's __init__ with the main message.
|
|
10
|
+
# This ensures `str(e)` works out of the box.
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
|
|
13
|
+
self.message: str = message
|
|
14
|
+
self.code: Optional[str | int] = code
|
|
15
|
+
self.details: Dict[str, Any] = details or {}
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
"""Custom string representation for logging."""
|
|
19
|
+
base_str: str = f"[{self.code}] {self.message}" if self.code else self.message
|
|
20
|
+
|
|
21
|
+
if self.details:
|
|
22
|
+
details_str: str = ", ".join(f"{k}={v!r}" for k, v in self.details.items())
|
|
23
|
+
return f"{base_str} (Details: {details_str})"
|
|
24
|
+
|
|
25
|
+
return base_str
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return self.__class__.__name__
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TaskDue(BaseModel):
|
|
7
|
+
date: str # Represents the date part, e.g., "2025-10-11"
|
|
8
|
+
string: str
|
|
9
|
+
lang: str
|
|
10
|
+
is_recurring: bool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Task(BaseModel):
|
|
14
|
+
id : str | None = None
|
|
15
|
+
content: str
|
|
16
|
+
description: str
|
|
17
|
+
project_id: str | None = None
|
|
18
|
+
priority: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskUpdate(BaseModel):
|
|
22
|
+
content: Optional[str] = None
|
|
23
|
+
description: Optional[str] = None
|
|
24
|
+
priority: Optional[int] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TodoistModel(BaseModel):
|
|
28
|
+
id: str
|
|
29
|
+
assigner_id: Optional[str] = Field(None, alias="assigned_by_uid")
|
|
30
|
+
assignee_id: Optional[str] = Field(None, alias="responsible_uid")
|
|
31
|
+
project_id: str
|
|
32
|
+
section_id: Optional[str] = None
|
|
33
|
+
parent_id: Optional[str] = None
|
|
34
|
+
order: int = Field(..., alias="child_order")
|
|
35
|
+
content: str
|
|
36
|
+
description: str
|
|
37
|
+
is_completed: bool = Field(..., alias="checked")
|
|
38
|
+
labels: list[str]
|
|
39
|
+
priority: int
|
|
40
|
+
comment_count: int = Field(..., alias="note_count")
|
|
41
|
+
creator_id: str = Field(..., alias="added_by_uid")
|
|
42
|
+
created_at: datetime = Field(..., alias="added_at")
|
|
43
|
+
due: Optional[TaskDue]
|
|
44
|
+
url: Optional[str] = None
|
|
45
|
+
duration: Optional[Any]
|
|
46
|
+
deadline: Optional[Any]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from .models import Task, TodoistModel, TaskUpdate
|
|
3
|
+
from .exception import TodoistException
|
|
4
|
+
import requests
|
|
5
|
+
from typing import List, NoReturn, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_tasks(api_token: str, base_url: str) -> List[TodoistModel] | NoReturn:
|
|
9
|
+
"""
|
|
10
|
+
Fetches active tasks from Todoist using the REST API directly (without SDK).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
endpoint = "tasks"
|
|
14
|
+
url = f"{base_url}{endpoint}"
|
|
15
|
+
|
|
16
|
+
headers = {
|
|
17
|
+
"Authorization": f"Bearer {api_token}",
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
response = requests.get(url, headers=headers)
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
data = response.json()
|
|
25
|
+
tasks_data = data.get("results", data)
|
|
26
|
+
|
|
27
|
+
tasks = [TodoistModel(**task) for task in tasks_data]
|
|
28
|
+
return tasks
|
|
29
|
+
except requests.exceptions.RequestException as e:
|
|
30
|
+
raise TodoistException("query failed")
|
|
31
|
+
except json.JSONDecodeError as e:
|
|
32
|
+
raise TodoistException("json decode error")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
raise TodoistException(f"Error: {e} {e.__class__.__name__}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_task_by_id(api_token: str, base_url: str, task_id: str) -> TodoistModel:
|
|
38
|
+
"""
|
|
39
|
+
Fetches a single task by its ID from Todoist.
|
|
40
|
+
"""
|
|
41
|
+
endpoint = f"tasks/{task_id}"
|
|
42
|
+
url = f"{base_url}{endpoint}"
|
|
43
|
+
|
|
44
|
+
headers = {
|
|
45
|
+
"Authorization": f"Bearer {api_token}",
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
response = requests.get(url, headers=headers)
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
task_data = response.json()
|
|
53
|
+
return TodoistModel(**task_data)
|
|
54
|
+
except requests.exceptions.RequestException as e:
|
|
55
|
+
raise e
|
|
56
|
+
except json.JSONDecodeError as e:
|
|
57
|
+
raise Exception(f"Error decoding JSON response: {e}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise e
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add_task(api_token: str, base_url: str, task: Task) -> Any | NoReturn:
|
|
63
|
+
"""
|
|
64
|
+
Creates a new task in Todoist using the REST API directly (without SDK).
|
|
65
|
+
"""
|
|
66
|
+
endpoint = "tasks"
|
|
67
|
+
url = f"{base_url}{endpoint}"
|
|
68
|
+
|
|
69
|
+
headers = {
|
|
70
|
+
"Authorization": f"Bearer {api_token}",
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
}
|
|
73
|
+
data = task.dict()
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
response = requests.post(url, headers=headers, data=json.dumps(data))
|
|
77
|
+
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
|
|
78
|
+
task_data = response.json()
|
|
79
|
+
return TodoistModel(**task_data)
|
|
80
|
+
except requests.exceptions.RequestException as e:
|
|
81
|
+
raise TodoistException("mutation failed")
|
|
82
|
+
except json.JSONDecodeError as e:
|
|
83
|
+
raise TodoistException(" json decode error")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise TodoistException(f"Error: {e} {e.__class__.__name__}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def update_task(
|
|
89
|
+
api_token: str, base_url: str, task_id: str, task: TaskUpdate
|
|
90
|
+
) -> TodoistModel:
|
|
91
|
+
"""
|
|
92
|
+
Updates a task in Todoist using the REST API directly (without SDK).
|
|
93
|
+
"""
|
|
94
|
+
endpoint = f"tasks/{task_id}"
|
|
95
|
+
url = f"{base_url}{endpoint}"
|
|
96
|
+
|
|
97
|
+
headers = {
|
|
98
|
+
"Authorization": f"Bearer {api_token}",
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
}
|
|
101
|
+
data = task.dict(exclude_unset=True)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
response = requests.post(url, headers=headers, data=json.dumps(data))
|
|
105
|
+
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
|
|
106
|
+
# The update endpoint returns 204 No Content on success, so we fetch the task again to return it
|
|
107
|
+
if response.status_code == 204:
|
|
108
|
+
return get_task_by_id(api_token, base_url, task_id)
|
|
109
|
+
task_data = response.json()
|
|
110
|
+
return TodoistModel(**task_data)
|
|
111
|
+
except requests.exceptions.RequestException as e:
|
|
112
|
+
raise e
|
|
113
|
+
except json.JSONDecodeError as e:
|
|
114
|
+
raise Exception(f"Error decoding JSON response: {e}")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
raise e
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: todoist-cli-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Todoist CLI - Manage your tasks from the command line
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pydantic>=2.12.3
|
|
8
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
9
|
+
Requires-Dist: questionary>=2.1.1
|
|
10
|
+
Requires-Dist: requests>=2.32.4
|
|
11
|
+
Requires-Dist: rich>=14.0.0
|
|
12
|
+
Requires-Dist: textual>=3.3.0
|
|
13
|
+
Requires-Dist: typer>=0.20.0
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/__init__.py
|
|
4
|
+
src/exception.py
|
|
5
|
+
src/models.py
|
|
6
|
+
src/todoist.py
|
|
7
|
+
src/todoist_cli_tool.egg-info/PKG-INFO
|
|
8
|
+
src/todoist_cli_tool.egg-info/SOURCES.txt
|
|
9
|
+
src/todoist_cli_tool.egg-info/dependency_links.txt
|
|
10
|
+
src/todoist_cli_tool.egg-info/entry_points.txt
|
|
11
|
+
src/todoist_cli_tool.egg-info/requires.txt
|
|
12
|
+
src/todoist_cli_tool.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|