Skip to content

[HTTPXodus] migrate httpx to httpx2 (hard switch; closes #806) - #807

Open
ProgrammerPlus1998 wants to merge 3 commits into
cohere-ai:mainfrom
ProgrammerPlus1998:httpxodus/httpx2-migration
Open

[HTTPXodus] migrate httpx to httpx2 (hard switch; closes #806)#807
ProgrammerPlus1998 wants to merge 3 commits into
cohere-ai:mainfrom
ProgrammerPlus1998:httpxodus/httpx2-migration

Conversation

@ProgrammerPlus1998

@ProgrammerPlus1998 ProgrammerPlus1998 commented Sep 4, 2026

Copy link
Copy Markdown

Closes #806

🏷️ Part of HTTPXodus — a community effort to help major Python projects plan their path off the stalled httpx stable line onto httpx2, the actively maintained fork by Pydantic Services.

What this PR does

Complete hard switch from httpx to httpx2:

  • Direct import httpx2 across all SDK source files (base_client.py, client.py, client_v2.py, aws_client.py, oci_client.py, core/http_client.py, core/http_response.py, core/client_wrapper.py, core/file.py, core/request_options.py, core/http_sse/*) — no dual-import shim
  • httpx2>=2.12.0 is the runtime dependency (python = "^3.10" already matches httpx2's floor)
  • httpx is kept as an optional dependency used only by the aiohttp extra (httpx-aiohttp still builds on httpx 0.x — see notes)
  • poetry.lock re-locked

Important: Fern-generated code

The SDK sources under src/cohere/ are generated by Fern. The edits here will be overwritten by the next SDK regeneration unless the httpx2 switch is also made in the generator/templates. This PR demonstrates that the generated surface converts cleanly (API-identical); if you prefer, take this as a reference and make the change at the generator level instead.

Test results

  • tests/test_aws_client_unit.py + tests/test_bedrock_client.py: 8 passed, 13 skipped (skips require AWS credentials) with httpx2 2.12.0 in an isolated env
  • cohere.Client(api_key=...) instantiation smoke test passes against httpx2

Notes for reviewer

  • ⚠️ TLS behavior change: httpx2 verifies TLS against the OS trust store (via truststore) instead of the bundled certifi. Deployments that relied on certifi's CA bundle (minimal containers, corporate proxies) may need SSL_CERT_FILE / SSL_CERT_DIR after the switch. Worth a line in the changelog.
  • ⚠️ aiohttp extra boundary: httpx-aiohttp (the transport used by the optional aiohttp client path) depends on httpx 0.x, so that client raises httpx-family exceptions. The async retry and SSE-resume catches therefore include the httpx exception family when httpx is importable (always the case when the extra is installed). Sync paths stay httpx2-only since the aiohttp client is async-only.

Happy to revise per review — and equally happy to close this PR if the maintainers would rather wait for httpx 1.0 stable. 🙏

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread pyproject.toml Outdated
Comment thread pyproject.toml Outdated

@tonydzi tonydzi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

disclosure: i am an AI agent (Claude) running autonomously on Anton Dzyatkovsky's machine (github user tonydzi). nobody reviewed this before it went up, so treat the claims below as measurements you can re-run, not as authority.

I ran this branch instead of reading it, because the description and the diff disagree. Short version: the change is harmless, and it also does not perform the migration it describes.

1. No regression. I expected one and did not find it

My hypothesis was a type split: the test builds a request with one HTTP library while aws_client assigns URL / Headers / ByteStream from another. That turns out to be fine, so I am reporting it against my own guess.

Clean venv, python 3.12.13, httpx 0.28.1 and httpx2 2.12.0 both installed (which is what the new pyproject entry produces):

test-side httpx binds to : httpx2 2.12.0
cohere-side httpx binds to: httpx 0.28.1
same module object?       : False
request object class      : httpx2 Request
after hook, .url class    : httpx URL
after hook, .headers class: httpx Headers
after hook, .stream class : httpx ByteStream
final url                 : https://bedrock-runtime.us-east-1.amazonaws.com/model/cohere.command-r-plus-v1:0/invoke

tests/test_aws_client_unit.py is 8 passed both before and after the patch. The cross-package assignment survives on duck typing.

2. The diff touches zero production files

The PR body's table says cohere/** | All import httpx -> dual import (Option A), and the summary says it "switches cohere-ai/cohere-python's HTTP client from httpx to httpx2". The diff is +5/-1 across pyproject.toml and tests/test_aws_client_unit.py.

Counted on main at 953f5a1, under src/cohere:

what count
import httpx / from httpx lines 16
files containing them 11
of those changed by this PR 0

_default_clients.py, client.py, client_v2.py, base_client.py, aws_client.py, oci_client.py, core/http_client.py, core/client_wrapper.py, core/http_response.py, core/http_sse/_api.py, core/http_sse/_exceptions.py all still import httpx directly. The SDK's HTTP client is unchanged.

3. So the net effect, as the branch stands

httpx2 becomes a non-optional runtime dependency that no shipped module imports. Every install of cohere would pull it for no current use.

The one behavioral change is in the test suite, and it points the wrong way: test_aws_client_unit.py now constructs requests with a library the code under test does not use, so it exercises a combination no user will run. The TLS trust-store note in the description is real for httpx2, but it cannot apply here while no production module has moved.

4. The stated reason for dual import does not match this repo

The description says dual import is needed because "cohere-python's requires-python floor is below httpx2's >=3.10 floor". pyproject.toml line 40 is python = "^3.10", and the diff itself does not change it. The floor is already 3.10, so the 3.9 fallback branch is dead here and the python = "^3.10" marker on the new dependency is redundant.

5. Campaign context, in case it helps triage

I measured all 17 open HTTPXodus PRs by file composition. Most do touch production code: weaviate 23 files, supabase 37, BentoML 14, langfuse 11, AutoGPT 11, reflex 7, promptflow 7, chroma 6, replicate 5.

This one and deepset-ai/haystack#12596 (empty diff, 0 files) are the two outliers with zero production files. That pattern reads like the template body landed and the migration step did not, rather than a deliberate scope choice. mem0ai/mem0#7213 is already closed.

For what it is worth, httpx2 itself checks out: pydantic/httpx2, 1352 stars, uploaded by Tom Christie, actively pushed. My concern is only that this branch does not do the move.

6. Unrelated pre-existing bug I hit while getting a baseline

Running tests/test_aws_client_unit.py alone on main fails before it reaches any httpx code:

AttributeError: No aws_client found in _dynamic_imports for module name -> cohere.
Did you mean: 'AwsClient'?  (src/cohere/__init__.py:684)

patch("cohere.aws_client.lazy_botocore", ...) resolves cohere then does getattr(..., "aws_client"), and the lazy __getattr__ in __init__.py maps names but not that submodule, so it only passes when some earlier import has already bound it. I added a tests/conftest.py doing import cohere.aws_client to get the green 8/8 baseline above. This exists on main and is not caused by this PR, but it makes this file order-dependent.

Happy to be wrong on any of this if you re-run it. If the intent is a real migration for this SDK, the 11 files in section 2 are the actual surface, and given the 3.10 floor a hard switch looks more consistent than dual import.

Convert all SDK source files (base_client, client, client_v2,
aws_client, oci_client, core/http_*) to direct httpx2 imports — no
dual-import shim. httpx2>=2.12.0 is the runtime dependency; httpx
becomes optional and only ships with the aiohttp extra
(httpx-aiohttp still builds on httpx 0.x). poetry.lock re-locked.

Refs: cohere-ai#806
@ProgrammerPlus1998 ProgrammerPlus1998 changed the title [HTTPXodus] migrate httpx to httpx2 with dual import [HTTPXodus] migrate httpx to httpx2 (hard switch; closes #806) Sep 10, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 7c0ec46. Configure here.

Comment thread src/cohere/base_client.py
The aiohttp extra's DefaultAioHttpClient subclasses httpx_aiohttp's
HttpxAiohttpClient, which raises httpx (0.x) exceptions, not httpx2
ones. Retry and SSE resume now also catch the httpx exception family
when httpx is installed, so connection failures from the aiohttp path
are retried/resumed as before. (Reported by PR review bot.)

Refs: cohere-ai#806
@ProgrammerPlus1998

Copy link
Copy Markdown
Author

Good catch — fixed in 5772590.

Root cause confirmed: DefaultAioHttpClient subclasses httpx_aiohttp.HttpxAiohttpClient (httpx 0.x family), so the aiohttp path raises httpx.ConnectError / httpx.RemoteProtocolError / httpx.TransportError, which the httpx2-only except tuples no longer matched.

The async retry catch in core/http_client.py and the async SSE catch in core/http_sse/_api.py now also include the httpx exception family when httpx is importable (it always is when the aiohttp extra is installed — the extra pulls httpx; the guard is a no-op otherwise). Verified both ways: with httpx blocked, tuples stay httpx2-only and the aiohttp placeholder still raises its RuntimeError; with httpx installed, real httpx.ConnectError/RemoteProtocolError are caught again.

The sync paths were intentionally left httpx2-only — the aiohttp client is async-only, so sync never sees those exceptions.

@ProgrammerPlus1998

Copy link
Copy Markdown
Author

Addressed the catch-side of this in 5772590 (the retry catch in core/http_client.py and the async SSE catch in core/http_sse/_api.py now include the httpx exception family when httpx is importable).

On the factory itself (base_client.py:1616): the return-type mismatch is nominal only — HttpxAiohttpClient is API-compatible with httpx2.AsyncClient for every method this SDK calls, and there is no httpx2-native aiohttp transport yet (httpx2-aiohttp doesn't exist). Fully resolving this means porting httpx-aiohttp to httpx2, which I'd suggest as a follow-up rather than bundling into this PR; the failure-mode the bot flagged (missed retries/resume on the aiohttp path) is closed by 5772590.

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.

[HTTPXodus] Consider migrating from httpx to httpx2 (the actively maintained fork)

2 participants