Skip to content

Commit 2314fd7

Browse files
committed
Modernize SDK for Todoist API v1
1 parent 65164c7 commit 2314fd7

29 files changed

+3049
-1761
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111

1212
- Use `dataclass-wizard` for object mapping
13+
- Update SDK to use the Todoist API v1 and corresponding changes
14+
- Remove deprecated `Task.sync_id`, `Task.comment_count`, and `Project.comment_count`
15+
- Replace `Task.is_completed` with `Task.completed_at`
16+
- Add support for `calendar` in `Project.view_style`
17+
- Rename `quick_add_task` to `add_task_quick`
18+
- Add `filter_tasks`, extracting that workflow from `get_tasks`
19+
- Paginate results via an `Iterator` in `get_tasks`, `filter_task`, `get_projects`,
20+
`get_collaborators`, `get_sections`, `get_comments`, `get_labels`, `get_shared_labels`
21+
- Remove support for `X-Request-Id` header, unused on the API level
22+
- Improve type hints and documentation
23+
- Hide internal modules and functions
24+
- Support for `note`, `reminder`, and `auto_reminder` in `add_task_quick`
1325

1426
### Fixes
1527

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ It is best to integrate to a release tag to ensure a stable dependency:
1010
```toml
1111
dependencies = [
1212
...
13-
"todoist-api-python>=8.0.0,<9",
13+
"todoist-api-python>=3.0.0,<4",
1414
...
1515
]
1616
```
@@ -27,20 +27,20 @@ An example of initializing the API client and fetching a user's tasks:
2727
from todoist_api_python.api_async import TodoistAPIAsync
2828
from todoist_api_python.api import TodoistAPI
2929

30-
# Fetch tasks asynchronously
31-
async def get_tasks_async():
32-
api = TodoistAPIAsync("YOURTOKEN")
30+
# Fetch tasks synchronously
31+
def get_tasks_sync():
32+
api = TodoistAPI("my token")
3333
try:
34-
tasks = await api.get_tasks()
34+
tasks = api.get_tasks()
3535
print(tasks)
3636
except Exception as error:
3737
print(error)
3838

39-
# Fetch tasks synchronously
40-
def get_tasks_sync():
41-
api = TodoistAPI("my token")
39+
# Fetch tasks asynchronously
40+
async def get_tasks_async():
41+
api = TodoistAPIAsync("YOURTOKEN")
4242
try:
43-
tasks = api.get_tasks()
43+
tasks = await api.get_tasks()
4444
print(tasks)
4545
except Exception as error:
4646
print(error)

pyproject.toml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ classifiers = [
1212
"Topic :: Software Development :: Libraries :: Python Modules",
1313
]
1414

15-
dependencies = ["requests>=2.32.3,<3", "dataclass-wizard>=0.35.0,<1.0"]
15+
dependencies = [
16+
"requests>=2.32.3,<3",
17+
"dataclass-wizard>=0.35.0,<1.0",
18+
"annotated-types",
19+
]
1620

1721
[project.urls]
1822
Homepage = "https://github.com/Doist/todoist-api-python"
@@ -118,16 +122,14 @@ ignore = [
118122
"D203", # incorrect-blank-line-before-class
119123
"D212", # multi-line-summary-first-line
120124
"PLR0913", # too-many-arguments
125+
"TRY00", # raise-vanilla-args
121126

122127
# All listed below are not intentional and should be fixed
123-
"D100", # undocumented-public-module
124-
"D101", # undocumented-public-class
125-
"D102", # undocumented-public-method
126-
"D103", # undocumented-public-function
127-
"D104", # undocumented-public-package
128-
"D105", # undocumented-magic-method
129-
"D107", # undocumented-public-init
130-
"ANN003", # missing-type-kwargs
128+
"D100", # undocumented-public-module
129+
"D101", # undocumented-public-class
130+
"D102", # undocumented-public-method
131+
"D103", # undocumented-public-function
132+
"D104", # undocumented-public-package
131133
]
132134

133135
[tool.ruff.lint.extend-per-file-ignores]

tests/conftest.py

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,31 @@
55
import pytest
66
import responses
77

8-
from tests.data.quick_add_responses import QUICK_ADD_RESPONSE_FULL
98
from tests.data.test_defaults import (
109
DEFAULT_AUTH_RESPONSE,
1110
DEFAULT_COLLABORATORS_RESPONSE,
1211
DEFAULT_COMMENT_RESPONSE,
1312
DEFAULT_COMMENTS_RESPONSE,
14-
DEFAULT_COMPLETED_ITEMS_RESPONSE,
1513
DEFAULT_LABEL_RESPONSE,
1614
DEFAULT_LABELS_RESPONSE,
1715
DEFAULT_PROJECT_RESPONSE,
1816
DEFAULT_PROJECTS_RESPONSE,
1917
DEFAULT_SECTION_RESPONSE,
2018
DEFAULT_SECTIONS_RESPONSE,
19+
DEFAULT_TASK_META_RESPONSE,
2120
DEFAULT_TASK_RESPONSE,
2221
DEFAULT_TASKS_RESPONSE,
2322
DEFAULT_TOKEN,
23+
PaginatedResults,
2424
)
2525
from todoist_api_python.api import TodoistAPI
2626
from todoist_api_python.api_async import TodoistAPIAsync
2727
from todoist_api_python.models import (
2828
AuthResult,
2929
Collaborator,
3030
Comment,
31-
CompletedItems,
3231
Label,
3332
Project,
34-
QuickAddResult,
3533
Section,
3634
Task,
3735
)
@@ -61,19 +59,32 @@ def default_task_response() -> dict[str, Any]:
6159
return DEFAULT_TASK_RESPONSE
6260

6361

62+
@pytest.fixture
63+
def default_task_meta() -> Task:
64+
return Task.from_dict(DEFAULT_TASK_META_RESPONSE)
65+
66+
67+
@pytest.fixture
68+
def default_task_meta_response() -> dict[str, Any]:
69+
return DEFAULT_TASK_META_RESPONSE
70+
71+
6472
@pytest.fixture
6573
def default_task() -> Task:
6674
return Task.from_dict(DEFAULT_TASK_RESPONSE)
6775

6876

6977
@pytest.fixture
70-
def default_tasks_response() -> list[dict[str, Any]]:
78+
def default_tasks_response() -> list[PaginatedResults]:
7179
return DEFAULT_TASKS_RESPONSE
7280

7381

7482
@pytest.fixture
75-
def default_tasks_list() -> list[Task]:
76-
return [Task.from_dict(obj) for obj in DEFAULT_TASKS_RESPONSE]
83+
def default_tasks_list() -> list[list[Task]]:
84+
return [
85+
[Task.from_dict(result) for result in response["results"]]
86+
for response in DEFAULT_TASKS_RESPONSE
87+
]
7788

7889

7990
@pytest.fixture
@@ -87,23 +98,29 @@ def default_project() -> Project:
8798

8899

89100
@pytest.fixture
90-
def default_projects_response() -> list[dict[str, Any]]:
101+
def default_projects_response() -> list[PaginatedResults]:
91102
return DEFAULT_PROJECTS_RESPONSE
92103

93104

94105
@pytest.fixture
95-
def default_projects_list() -> list[Project]:
96-
return [Project.from_dict(obj) for obj in DEFAULT_PROJECTS_RESPONSE]
106+
def default_projects_list() -> list[list[Project]]:
107+
return [
108+
[Project.from_dict(result) for result in response["results"]]
109+
for response in DEFAULT_PROJECTS_RESPONSE
110+
]
97111

98112

99113
@pytest.fixture
100-
def default_collaborators_response() -> list[dict[str, Any]]:
114+
def default_collaborators_response() -> list[PaginatedResults]:
101115
return DEFAULT_COLLABORATORS_RESPONSE
102116

103117

104118
@pytest.fixture
105-
def default_collaborators_list() -> list[Collaborator]:
106-
return [Collaborator.from_dict(obj) for obj in DEFAULT_COLLABORATORS_RESPONSE]
119+
def default_collaborators_list() -> list[list[Collaborator]]:
120+
return [
121+
[Collaborator.from_dict(result) for result in response["results"]]
122+
for response in DEFAULT_COLLABORATORS_RESPONSE
123+
]
107124

108125

109126
@pytest.fixture
@@ -117,13 +134,16 @@ def default_section() -> Section:
117134

118135

119136
@pytest.fixture
120-
def default_sections_response() -> list[dict[str, Any]]:
137+
def default_sections_response() -> list[PaginatedResults]:
121138
return DEFAULT_SECTIONS_RESPONSE
122139

123140

124141
@pytest.fixture
125-
def default_sections_list() -> list[Section]:
126-
return [Section.from_dict(obj) for obj in DEFAULT_SECTIONS_RESPONSE]
142+
def default_sections_list() -> list[list[Section]]:
143+
return [
144+
[Section.from_dict(result) for result in response["results"]]
145+
for response in DEFAULT_SECTIONS_RESPONSE
146+
]
127147

128148

129149
@pytest.fixture
@@ -137,13 +157,16 @@ def default_comment() -> Comment:
137157

138158

139159
@pytest.fixture
140-
def default_comments_response() -> list[dict[str, Any]]:
160+
def default_comments_response() -> list[PaginatedResults]:
141161
return DEFAULT_COMMENTS_RESPONSE
142162

143163

144164
@pytest.fixture
145-
def default_comments_list() -> list[Comment]:
146-
return [Comment.from_dict(obj) for obj in DEFAULT_COMMENTS_RESPONSE]
165+
def default_comments_list() -> list[list[Comment]]:
166+
return [
167+
[Comment.from_dict(result) for result in response["results"]]
168+
for response in DEFAULT_COMMENTS_RESPONSE
169+
]
147170

148171

149172
@pytest.fixture
@@ -157,23 +180,26 @@ def default_label() -> Label:
157180

158181

159182
@pytest.fixture
160-
def default_labels_response() -> list[dict[str, Any]]:
183+
def default_labels_response() -> list[PaginatedResults]:
161184
return DEFAULT_LABELS_RESPONSE
162185

163186

164187
@pytest.fixture
165-
def default_labels_list() -> list[Label]:
166-
return [Label.from_dict(obj) for obj in DEFAULT_LABELS_RESPONSE]
188+
def default_labels_list() -> list[list[Label]]:
189+
return [
190+
[Label.from_dict(result) for result in response["results"]]
191+
for response in DEFAULT_LABELS_RESPONSE
192+
]
167193

168194

169195
@pytest.fixture
170196
def default_quick_add_response() -> dict[str, Any]:
171-
return QUICK_ADD_RESPONSE_FULL
197+
return DEFAULT_TASK_RESPONSE
172198

173199

174200
@pytest.fixture
175-
def default_quick_add_result() -> QuickAddResult:
176-
return QuickAddResult.from_quick_add_response(QUICK_ADD_RESPONSE_FULL)
201+
def default_quick_add_result() -> Task:
202+
return Task.from_dict(DEFAULT_TASK_RESPONSE)
177203

178204

179205
@pytest.fixture
@@ -184,13 +210,3 @@ def default_auth_response() -> dict[str, Any]:
184210
@pytest.fixture
185211
def default_auth_result() -> AuthResult:
186212
return AuthResult.from_dict(DEFAULT_AUTH_RESPONSE)
187-
188-
189-
@pytest.fixture
190-
def default_completed_items_response() -> dict[str, Any]:
191-
return DEFAULT_COMPLETED_ITEMS_RESPONSE
192-
193-
194-
@pytest.fixture
195-
def default_completed_items() -> CompletedItems:
196-
return CompletedItems.from_dict(DEFAULT_COMPLETED_ITEMS_RESPONSE)

tests/data/quick_add_responses.py

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)