Skip to content

Commit 0245bf5

Browse files
committed
Migrate async SDK to httpx
Move the async client to native httpx.AsyncClient I/O and remove\nexecutor-based async wrappers from API flows.\n\nAlso add respx-backed test infrastructure and migrate API tests\nthat exercise async behavior through the new transport stack.
1 parent 875729f commit 0245bf5

16 files changed

Lines changed: 1316 additions & 505 deletions

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ classifiers = [
1313
]
1414

1515
dependencies = [
16-
"requests>=2.32.3,<3",
16+
"httpx>=0.28.1,<1",
1717
"dataclass-wizard>=0.35.0,<1.0",
1818
"annotated-types",
1919
]
@@ -32,8 +32,7 @@ dev = [
3232
"tox-uv>=1.25.0,<2",
3333
"mypy~=1.11",
3434
"ruff>=0.11.0,<0.12",
35-
"responses>=0.25.3,<0.26",
36-
"types-requests~=2.32",
35+
"respx>=0.22.0,<0.23",
3736
]
3837

3938
docs = [

tests/conftest.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any
44

55
import pytest
6-
import responses
6+
import pytest_asyncio
77

88
from tests.data.test_defaults import (
99
DEFAULT_AUTH_RESPONSE,
@@ -24,6 +24,7 @@
2424
PaginatedItems,
2525
PaginatedResults,
2626
)
27+
from tests.utils.http_mock import RequestsMock
2728
from todoist_api_python.api import TodoistAPI
2829
from todoist_api_python.api_async import TodoistAPIAsync
2930
from todoist_api_python.models import (
@@ -37,23 +38,30 @@
3738
)
3839

3940
if TYPE_CHECKING:
40-
from collections.abc import Iterator
41+
from collections.abc import AsyncIterator, Iterator
42+
43+
import respx
4144

4245

4346
@pytest.fixture
44-
def requests_mock() -> Iterator[responses.RequestsMock]:
45-
with responses.RequestsMock() as requests_mock:
46-
yield requests_mock
47+
def requests_mock(respx_mock: respx.MockRouter) -> Iterator[RequestsMock]:
48+
mock = RequestsMock(respx_mock)
49+
yield mock
50+
mock.assert_all_called()
4751

4852

4953
@pytest.fixture
50-
def todoist_api() -> TodoistAPI:
51-
return TodoistAPI(DEFAULT_TOKEN)
54+
def todoist_api(respx_mock: respx.MockRouter) -> Iterator[TodoistAPI]:
55+
with TodoistAPI(DEFAULT_TOKEN) as api:
56+
yield api
5257

5358

54-
@pytest.fixture
55-
def todoist_api_async() -> TodoistAPIAsync:
56-
return TodoistAPIAsync(DEFAULT_TOKEN)
59+
@pytest_asyncio.fixture
60+
async def todoist_api_async(
61+
respx_mock: respx.MockRouter,
62+
) -> AsyncIterator[TodoistAPIAsync]:
63+
async with TodoistAPIAsync(DEFAULT_TOKEN) as api:
64+
yield api
5765

5866

5967
@pytest.fixture

tests/test_api_comments.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import TYPE_CHECKING, Any
44

55
import pytest
6-
import responses
76

87
from tests.data.test_defaults import (
98
DEFAULT_API_URL,
@@ -19,6 +18,7 @@
1918
from todoist_api_python.models import Attachment
2019

2120
if TYPE_CHECKING:
21+
from tests.utils.http_mock import RequestsMock
2222
from todoist_api_python.api import TodoistAPI
2323
from todoist_api_python.api_async import TodoistAPIAsync
2424

@@ -29,15 +29,15 @@
2929
async def test_get_comment(
3030
todoist_api: TodoistAPI,
3131
todoist_api_async: TodoistAPIAsync,
32-
requests_mock: responses.RequestsMock,
32+
requests_mock: RequestsMock,
3333
default_comment_response: dict[str, Any],
3434
default_comment: Comment,
3535
) -> None:
3636
comment_id = "6X7rM8997g3RQmvh"
3737
endpoint = f"{DEFAULT_API_URL}/comments/{comment_id}"
3838

3939
requests_mock.add(
40-
method=responses.GET,
40+
method="GET",
4141
url=endpoint,
4242
json=default_comment_response,
4343
status=200,
@@ -59,7 +59,7 @@ async def test_get_comment(
5959
async def test_get_comments(
6060
todoist_api: TodoistAPI,
6161
todoist_api_async: TodoistAPIAsync,
62-
requests_mock: responses.RequestsMock,
62+
requests_mock: RequestsMock,
6363
default_comments_response: list[PaginatedResults],
6464
default_comments_list: list[list[Comment]],
6565
) -> None:
@@ -69,7 +69,7 @@ async def test_get_comments(
6969
cursor: str | None = None
7070
for page in default_comments_response:
7171
requests_mock.add(
72-
method=responses.GET,
72+
method="GET",
7373
url=endpoint,
7474
json=page,
7575
status=200,
@@ -102,7 +102,7 @@ async def test_get_comments(
102102
async def test_add_comment(
103103
todoist_api: TodoistAPI,
104104
todoist_api_async: TodoistAPIAsync,
105-
requests_mock: responses.RequestsMock,
105+
requests_mock: RequestsMock,
106106
default_comment_response: dict[str, Any],
107107
default_comment: Comment,
108108
) -> None:
@@ -116,7 +116,7 @@ async def test_add_comment(
116116
)
117117

118118
requests_mock.add(
119-
method=responses.POST,
119+
method="POST",
120120
url=f"{DEFAULT_API_URL}/comments",
121121
json=default_comment_response,
122122
status=200,
@@ -156,7 +156,7 @@ async def test_add_comment(
156156
async def test_update_comment(
157157
todoist_api: TodoistAPI,
158158
todoist_api_async: TodoistAPIAsync,
159-
requests_mock: responses.RequestsMock,
159+
requests_mock: RequestsMock,
160160
default_comment: Comment,
161161
) -> None:
162162
args = {
@@ -165,7 +165,7 @@ async def test_update_comment(
165165
updated_comment_dict = default_comment.to_dict() | args
166166

167167
requests_mock.add(
168-
method=responses.POST,
168+
method="POST",
169169
url=f"{DEFAULT_API_URL}/comments/{default_comment.id}",
170170
json=updated_comment_dict,
171171
status=200,
@@ -189,13 +189,13 @@ async def test_update_comment(
189189
async def test_delete_comment(
190190
todoist_api: TodoistAPI,
191191
todoist_api_async: TodoistAPIAsync,
192-
requests_mock: responses.RequestsMock,
192+
requests_mock: RequestsMock,
193193
) -> None:
194194
comment_id = "6X7rM8997g3RQmvh"
195195
endpoint = f"{DEFAULT_API_URL}/comments/{comment_id}"
196196

197197
requests_mock.add(
198-
method=responses.DELETE,
198+
method="DELETE",
199199
url=endpoint,
200200
status=204,
201201
match=[auth_matcher(), request_id_matcher()],

tests/test_api_completed_tasks.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
UTC = timezone.utc
1111

1212
import pytest
13-
import responses
1413

1514
from tests.data.test_defaults import DEFAULT_API_URL, PaginatedItems
1615
from tests.utils.test_utils import (
@@ -22,6 +21,7 @@
2221
from todoist_api_python._core.utils import format_datetime
2322

2423
if TYPE_CHECKING:
24+
from tests.utils.http_mock import RequestsMock
2525
from todoist_api_python.api import TodoistAPI
2626
from todoist_api_python.api_async import TodoistAPIAsync
2727
from todoist_api_python.models import Task
@@ -31,7 +31,7 @@
3131
async def test_get_completed_tasks_by_due_date(
3232
todoist_api: TodoistAPI,
3333
todoist_api_async: TodoistAPIAsync,
34-
requests_mock: responses.RequestsMock,
34+
requests_mock: RequestsMock,
3535
default_completed_tasks_response: list[PaginatedItems],
3636
default_completed_tasks_list: list[list[Task]],
3737
) -> None:
@@ -52,7 +52,7 @@ async def test_get_completed_tasks_by_due_date(
5252
cursor: str | None = None
5353
for page in default_completed_tasks_response:
5454
requests_mock.add(
55-
method=responses.GET,
55+
method="GET",
5656
url=endpoint,
5757
json=page,
5858
status=200,
@@ -91,7 +91,7 @@ async def test_get_completed_tasks_by_due_date(
9191
async def test_get_completed_tasks_by_completion_date(
9292
todoist_api: TodoistAPI,
9393
todoist_api_async: TodoistAPIAsync,
94-
requests_mock: responses.RequestsMock,
94+
requests_mock: RequestsMock,
9595
default_completed_tasks_response: list[PaginatedItems],
9696
default_completed_tasks_list: list[list[Task]],
9797
) -> None:
@@ -112,7 +112,7 @@ async def test_get_completed_tasks_by_completion_date(
112112
cursor: str | None = None
113113
for page in default_completed_tasks_response:
114114
requests_mock.add(
115-
method=responses.GET,
115+
method="GET",
116116
url=endpoint,
117117
json=page,
118118
status=200,

tests/test_api_labels.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import TYPE_CHECKING, Any
44

55
import pytest
6-
import responses
76

87
from tests.data.test_defaults import DEFAULT_API_URL, PaginatedResults
98
from tests.utils.test_utils import (
@@ -15,6 +14,7 @@
1514
)
1615

1716
if TYPE_CHECKING:
17+
from tests.utils.http_mock import RequestsMock
1818
from todoist_api_python.api import TodoistAPI
1919
from todoist_api_python.api_async import TodoistAPIAsync
2020
from todoist_api_python.models import Label
@@ -24,15 +24,15 @@
2424
async def test_get_label(
2525
todoist_api: TodoistAPI,
2626
todoist_api_async: TodoistAPIAsync,
27-
requests_mock: responses.RequestsMock,
27+
requests_mock: RequestsMock,
2828
default_label_response: dict[str, Any],
2929
default_label: Label,
3030
) -> None:
3131
label_id = "6X7rM8997g3RQmvh"
3232
endpoint = f"{DEFAULT_API_URL}/labels/{label_id}"
3333

3434
requests_mock.add(
35-
method=responses.GET,
35+
method="GET",
3636
url=endpoint,
3737
json=default_label_response,
3838
status=200,
@@ -54,7 +54,7 @@ async def test_get_label(
5454
async def test_get_labels(
5555
todoist_api: TodoistAPI,
5656
todoist_api_async: TodoistAPIAsync,
57-
requests_mock: responses.RequestsMock,
57+
requests_mock: RequestsMock,
5858
default_labels_response: list[PaginatedResults],
5959
default_labels_list: list[list[Label]],
6060
) -> None:
@@ -63,7 +63,7 @@ async def test_get_labels(
6363
cursor: str | None = None
6464
for page in default_labels_response:
6565
requests_mock.add(
66-
method=responses.GET,
66+
method="GET",
6767
url=endpoint,
6868
json=page,
6969
status=200,
@@ -92,7 +92,7 @@ async def test_get_labels(
9292
async def test_search_labels(
9393
todoist_api: TodoistAPI,
9494
todoist_api_async: TodoistAPIAsync,
95-
requests_mock: responses.RequestsMock,
95+
requests_mock: RequestsMock,
9696
default_labels_response: list[PaginatedResults],
9797
default_labels_list: list[list[Label]],
9898
) -> None:
@@ -102,7 +102,7 @@ async def test_search_labels(
102102
cursor: str | None = None
103103
for page in default_labels_response:
104104
requests_mock.add(
105-
method=responses.GET,
105+
method="GET",
106106
url=endpoint,
107107
json=page,
108108
status=200,
@@ -135,14 +135,14 @@ async def test_search_labels(
135135
async def test_add_label_minimal(
136136
todoist_api: TodoistAPI,
137137
todoist_api_async: TodoistAPIAsync,
138-
requests_mock: responses.RequestsMock,
138+
requests_mock: RequestsMock,
139139
default_label_response: dict[str, Any],
140140
default_label: Label,
141141
) -> None:
142142
label_name = "A Label"
143143

144144
requests_mock.add(
145-
method=responses.POST,
145+
method="POST",
146146
url=f"{DEFAULT_API_URL}/labels",
147147
json=default_label_response,
148148
status=200,
@@ -168,7 +168,7 @@ async def test_add_label_minimal(
168168
async def test_add_label_full(
169169
todoist_api: TodoistAPI,
170170
todoist_api_async: TodoistAPIAsync,
171-
requests_mock: responses.RequestsMock,
171+
requests_mock: RequestsMock,
172172
default_label_response: dict[str, Any],
173173
default_label: Label,
174174
) -> None:
@@ -180,7 +180,7 @@ async def test_add_label_full(
180180
}
181181

182182
requests_mock.add(
183-
method=responses.POST,
183+
method="POST",
184184
url=f"{DEFAULT_API_URL}/labels",
185185
json=default_label_response,
186186
status=200,
@@ -206,7 +206,7 @@ async def test_add_label_full(
206206
async def test_update_label(
207207
todoist_api: TodoistAPI,
208208
todoist_api_async: TodoistAPIAsync,
209-
requests_mock: responses.RequestsMock,
209+
requests_mock: RequestsMock,
210210
default_label: Label,
211211
) -> None:
212212
args: dict[str, Any] = {
@@ -215,7 +215,7 @@ async def test_update_label(
215215
updated_label_dict = default_label.to_dict() | args
216216

217217
requests_mock.add(
218-
method=responses.POST,
218+
method="POST",
219219
url=f"{DEFAULT_API_URL}/labels/{default_label.id}",
220220
json=updated_label_dict,
221221
status=200,
@@ -237,13 +237,13 @@ async def test_update_label(
237237
async def test_delete_label(
238238
todoist_api: TodoistAPI,
239239
todoist_api_async: TodoistAPIAsync,
240-
requests_mock: responses.RequestsMock,
240+
requests_mock: RequestsMock,
241241
) -> None:
242242
label_id = "6X7rM8997g3RQmvh"
243243
endpoint = f"{DEFAULT_API_URL}/labels/{label_id}"
244244

245245
requests_mock.add(
246-
method=responses.DELETE,
246+
method="DELETE",
247247
url=endpoint,
248248
status=204,
249249
)

0 commit comments

Comments
 (0)