Migrate SDK HTTP stack from requests to httpx#221
Migrate SDK HTTP stack from requests to httpx#221goncalossilva wants to merge 5 commits intomainfrom
Conversation
6167b48 to
f9dc425
Compare
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.
f9dc425 to
44d5c4e
Compare
Switch TodoistAPI and authentication sync paths from requests\nto httpx.Client and align sync error semantics with\nhttpx.HTTPStatusError.\n\nUpdate sync-focused tests to use the respx-backed request\nmocking setup.
Update docs and changelog to describe the requests-to-httpx\ntransition, constructor client arguments, and new HTTP error\nhandling guidance.
Use declarative respx lookups (params__eq, headers__contains, json__eq) via mock_route request_* arguments and response_* payload arguments. Set API fixtures to deterministic request IDs so request-header assertions remain fully declarative and consistent across sync and async tests.
930cfc7 to
26fde1d
Compare
Add a pure kwargs_without_none helper and reuse it across TodoistAPI and TodoistAPIAsync to reduce duplicated None-filtering logic in request params/data preparation.
26fde1d to
3112144
Compare
There was a problem hiding this comment.
Pull request overview
This PR successfully migrates the Todoist Python SDK's HTTP stack from requests to httpx, enabling true async HTTP I/O and removing the problematic executor-based async wrappers that blocked the event loop. The migration is comprehensive, covering both sync and async clients, authentication helpers, tests, and documentation.
Changes:
- Replaced
requestswithhttpxfor HTTP operations (sync:httpx.Client, async:httpx.AsyncClient) - Removed executor-based async wrappers (
run_async,generate_async) in favor of native async HTTP calls - Migrated test mocking from
responsestorespx
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Added httpx dependencies (httpx, httpcore, h11, anyio); removed requests and types-requests; replaced responses with respx |
| pyproject.toml | Updated dependencies: httpx>=0.28.1 instead of requests; respx instead of responses; removed types-requests |
| todoist_api_python/types.py | New shared type aliases module for ColorString, LanguageCode, ViewStyle (extracted from api.py) |
| todoist_api_python/_core/type_aliases.py | Re-exports type aliases from types.py for backwards compatibility |
| todoist_api_python/_core/utils.py | Removed run_async and generate_async functions; added kwargs_without_none helper |
| todoist_api_python/_core/http_requests.py | Complete rewrite using httpx.Client/AsyncClient; added true async variants (get_async, post_async, delete_async); uses httpx.Timeout instead of tuple |
| todoist_api_python/_core/http_headers.py | Removed Content-Type header creation (httpx handles this automatically with json= parameter) |
| todoist_api_python/api.py | Updated to use httpx.Client; replaced manual dict building with kwargs_without_none; removed noqa PLR0912 comments |
| todoist_api_python/api_async.py | Complete rewrite with true async HTTP; added proper client lifecycle management (aenter, aexit, close, del with ResourceWarning); uses AsyncIterator instead of AsyncGenerator; implements AsyncResultsPaginator |
| todoist_api_python/authentication.py | Migrated to httpx with context managers for client lifecycle; added async variants; uses _managed_client and _managed_async_client helpers |
| tests/* | Migrated all tests from responses to respx; updated mocking utilities; added async client lifecycle test; fixtures now use context managers |
| docs/* | Added async usage guidance; documented breaking changes; updated authentication examples |
| README.md | Added migration guide from 3.x to 4.x; documented async client lifecycle requirements |
| CHANGELOG.md | Documented all breaking changes for version 4.0.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Do we need this file? I don't see it being used anywhere...
| - **Breaking**: `TodoistAPIAsync` now accepts an optional `client: httpx.AsyncClient` instead of `session: requests.Session`. | ||
| - **Breaking**: API errors now raise `httpx.HTTPStatusError` instead of `requests.exceptions.HTTPError`. | ||
| - **Breaking**: Authentication helpers now accept optional `httpx.Client` / `httpx.AsyncClient` instances instead of `session: requests.Session`. | ||
|
|
There was a problem hiding this comment.
Maybe we should also mention that all AsyncGenerator became AsyncIterator in the breaking changes, in case people have typed variables with the former?
|
|
||
| def _parse_response( | ||
| response: httpx.Response, | ||
| _result_type: type[T] | None = None, |
There was a problem hiding this comment.
What is the purpose of _result_type and the result_type we pass around in other functions?
| version = "3.2.1" | ||
| description = "Official Python SDK for the Todoist API." | ||
| authors = [{ name = "Doist Developers", email = "dev@doist.com" }] | ||
| requires-python = "~=3.9" |
There was a problem hiding this comment.
We probably can fix this, too. I get this when running uv:
warning: The
requires-pythonspecifier (~=3.9) intodoist-api-pythonuses the tilde specifier (~=) without a patch version. This will be interpreted as>=3.9, <4. Did you mean~=3.9.0to constrain the version as>=3.9.0, <3.10? We recommend only using the tilde specifier with a patch version to avoid ambiguity.
lefcha
left a comment
There was a problem hiding this comment.
Overall looks good. I only had some comments about minor things (see inline comments).
Summary
This PR migrates the SDK HTTP stack from
requeststohttpxacross async AND sync clients.What changed
httpx:TodoistAPI(..., client: httpx.Client | None = None)TodoistAPIAsync(..., client: httpx.AsyncClient | None = None)httpx(get_auth_token,revoke_auth_token, async variants)._core/http_requests.py) tohttpx.responses/requests-style mocks torespx.Breaking changes
sessionconstructor arg is replaced byclient(no compatibility alias):TodoistAPI(session=...)->TodoistAPI(client=...)TodoistAPIAsync(session=...)->TodoistAPIAsync(client=...)httpx.Client/httpx.AsyncClientinstances instead ofsession: requests.Session.httpx.HTTPStatusError.Next steps
Major version bump and release 4.0.0.