Skip to content

Migrate SDK HTTP stack from requests to httpx#221

Merged
goncalossilva merged 6 commits intomainfrom
goncalossilva/httpx
Feb 20, 2026
Merged

Migrate SDK HTTP stack from requests to httpx#221
goncalossilva merged 6 commits intomainfrom
goncalossilva/httpx

Conversation

@goncalossilva
Copy link
Member

@goncalossilva goncalossilva commented Feb 13, 2026

Summary

This PR migrates the SDK HTTP stack from requests to httpx across async AND sync clients.

What changed

  • Replaced runtime HTTP client usage with httpx:
    • TodoistAPI(..., client: httpx.Client | None = None)
    • TodoistAPIAsync(..., client: httpx.AsyncClient | None = None)
  • Removed executor-based async request wrappers from API I/O paths (true async HTTP calls now: closes Running Synchronous Code as Async Using Lambdas (Code Smell) #162).
  • Migrated auth helpers to httpx (get_auth_token, revoke_auth_token, async variants).
  • Updated low-level HTTP helpers (_core/http_requests.py) to httpx.
  • Migrated tests from responses/requests-style mocks to respx.
  • Added async-client lifecycle coverage and explicit async close guidance.
  • Added shared constrained type aliases for API annotations.
  • Updated docs/changelog for the migration.

Breaking changes

  • session constructor arg is replaced by client (no compatibility alias):
    • TodoistAPI(session=...) -> TodoistAPI(client=...)
    • TodoistAPIAsync(session=...) -> TodoistAPIAsync(client=...)
  • Authentication helpers now accept optional httpx.Client / httpx.AsyncClient instances instead of session: requests.Session.
  • Exceptions to catch are now httpx.HTTPStatusError.

Next steps

Major version bump and release 4.0.0.

@goncalossilva goncalossilva force-pushed the goncalossilva/httpx branch 6 times, most recently from 6167b48 to f9dc425 Compare February 13, 2026 18:18
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.
@goncalossilva goncalossilva force-pushed the goncalossilva/httpx branch 4 times, most recently from 26fde1d to 3112144 Compare February 13, 2026 18:53
@goncalossilva goncalossilva requested a review from lefcha February 13, 2026 18:59
@goncalossilva goncalossilva marked this pull request as ready for review February 13, 2026 19:00
@goncalossilva goncalossilva requested a review from a team as a code owner February 13, 2026 19:00
Copilot AI review requested due to automatic review settings February 13, 2026 19:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 requests with httpx for 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 responses to respx

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.

Copy link
Collaborator

@lefcha lefcha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. I only had some comments about minor things (see inline comments).

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.
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.
No more blind type casting and type witnesses.
@goncalossilva goncalossilva merged commit 7098d3a into main Feb 20, 2026
5 checks passed
@goncalossilva goncalossilva deleted the goncalossilva/httpx branch February 20, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running Synchronous Code as Async Using Lambdas (Code Smell)

3 participants