Skip to content

feat(client): Add experimental runtime support for HTTPX2 clients - #3524

Merged
apcha-oai merged 15 commits into
nextfrom
apcha/httpx2-opt-in-experiment
Jul 21, 2026
Merged

feat(client): Add experimental runtime support for HTTPX2 clients#3524
apcha-oai merged 15 commits into
nextfrom
apcha/httpx2-opt-in-experiment

Conversation

@apcha-oai

@apcha-oai apcha-oai commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Refs #3375.

Summary

Adds experimental runtime support for using an HTTPX2 client with the 2.x SDK:

pip install 'openai[httpx2]'
from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAsyncHttpx2Client

client = OpenAI(http_client=DefaultHttpx2Client())
async_client = AsyncOpenAI(http_client=DefaultAsyncHttpx2Client())

HTTPX remains installed, imported, and authoritative for the SDK's public transport types. Existing HTTPX clients and helpers continue to work unchanged. This is an explicit runtime opt-in, not a default-transport or typing migration: generated/parsed API models remain accurately typed, while raw requests, responses, streams, and transport exceptions can be HTTPX2 objects even where annotations still describe HTTPX.

The implementation keeps the compatibility boundary small:

  • accepts sync, async, and module-level HTTPX2 clients and preserves their native request/response/exception family;
  • supplies DefaultHttpx2Client and DefaultAsyncHttpx2Client with the SDK defaults, while retaining the existing HTTPX and aiohttp helpers;
  • handles the concrete request, timeout, URL, response-casting, retry, streaming, auth, and provider differences needed at runtime;
  • makes workload-identity token exchange follow an explicitly selected HTTPX2 client (including async clients, whose exchange still runs synchronously in the existing worker-thread path), without changing behavior merely because HTTPX2 happens to be installed.

The httpx2 extra is available on Python 3.10+ and scopes its resolver requirements to the opt-in path (httpx>=0.25.1,<1, httpx2>=2.7,<3, and anyio>=4.10,<5). Base installations retain the existing Python, HTTPX, and AnyIO floors. On Python 3.9 the extra is marker-skipped and invoking an HTTPX2 helper produces an actionable error. Intentionally mixed HTTPX/HTTPX2 transports or auth implementations are out of scope.

Gaps

  • This pr does not address aiohttp+ httpx2; that can be a future addition if there is interest. This would require a separate addon and for us to vendor in some of the implementation; so avoiding in this PR.
  • Types stay on httpx, and httpx is still required to be an installed package.
    • reasoning: we considered httpx | httpx2 as a migration shim, or returning httpx2 type annotations. Will continue to evaluate the ecosystem, although httpx2 types will unfortunately likely require many updates from downstream packages. Saving those considerations for potential future major versions.
    • For now, we do not address httpx.timeout annotations in generated code as well

Testing

The HTTPX2 CI lane runs the normal suite with native HTTPX2 sync/async clients in both Pydantic modes. Existing RESPX-backed cases are exercised through a small, test-only bridge: HTTPX2 uses a native MockTransport, the bridge translates only at the RESPX matching/callback boundary, and the SDK still builds and receives native HTTPX2 requests/responses. This keeps generated binary/raw/streaming, retry, auth, Azure, Bedrock, and snapshot coverage shared instead of maintaining duplicate tests; aiohttp remains a separate HTTPX-family path.

Focused native tests also cover client defaults, direct injection, raw/SSE/multipart, retries and exception families, hooks/mounts/proxies, provider auth, workload-identity exchange/cache/401 refresh, and base-only/extra resolver behavior. Local full-suite runs pass with HTTPX2/Pydantic 2 and the HTTPX2 floor/Pydantic 1; the ordinary HTTPX suite and lint/type checks remain clean.

@apcha-oai
apcha-oai marked this pull request as ready for review July 21, 2026 17:44
@apcha-oai
apcha-oai requested a review from a team as a code owner July 21, 2026 17:44

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 318d4e1323

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openai/_httpx2.py
Comment thread src/openai/_base_client.py

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The opt-in runtime path is thoughtfully scoped: HTTPX2 stays lazy/optional, base dependency floors remain unchanged, native request/response/exception families are preserved, and the focused packaging + provider + streaming coverage is unusually thorough. I also re-verified that the latest commit addresses the existing WebSocket URL and timeout-header findings.

I found two gaps against the explicit compatibility/default-migration goals:

  1. Workload identity records the transport family from the constructor argument rather than the client that was actually selected. That works for today's explicit opt-in, but silently falls back to HTTPX token exchange as soon as the default wrapper is changed to HTTPX2.
  2. CI covers HTTPX on the 3.9 floor (where the HTTPX2 extra is marker-skipped) and HTTPX2 on 3.12, but not the full ordinary-HTTPX path on 3.12 with HTTPX2 installed/importable. Because several adapters widen behavior based on whether httpx2 is loaded, that coexistence lane is the one that protects existing customers.

I locally ran the focused wheel/runtime tests under Python 3.12 with HTTPX2 2.7.0: 28 passed, 5 skipped (optional dependencies).

Comment thread src/openai/_client.py Outdated
Comment thread .github/workflows/ci.yml

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed the updated head. The two prior blockers are resolved: workload identity now derives HTTPX2 selection from the instantiated sync/async client (with regression coverage for a future default flip), and the Python 3.12 HTTPX2 lane now runs the full suite once with ordinary HTTPX selected before the native HTTPX2 run. I also rechecked the opt-in/lazy-loading behavior and the earlier WebSocket URL and timeout-header fixes.

Evidence: CI on this exact SHA passed lint, build/wheel validation, the normal suite, the HTTPX-with-HTTPX2-installed coexistence suite, and the HTTPX2 suite. Local Python 3.12.9 focused verification passed 32 tests with 3 optional RESPX skips; Ruff formatting/lint and diff checks passed. No blocking findings.

@apcha-oai apcha-oai changed the title Add experimental runtime support for HTTPX2 clients feat(client): Add experimental runtime support for HTTPX2 clients Jul 21, 2026
@apcha-oai
apcha-oai merged commit 317260c into next Jul 21, 2026
12 checks passed
@apcha-oai
apcha-oai deleted the apcha/httpx2-opt-in-experiment branch July 21, 2026 20:57
@stainless-app stainless-app Bot mentioned this pull request Jul 21, 2026
stainless-app Bot pushed a commit that referenced this pull request Jul 22, 2026
)

Refs #3375.

## Summary

Adds experimental runtime support for using an HTTPX2 client with the
2.x SDK:

```sh
pip install 'openai[httpx2]'
```

```python
from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAsyncHttpx2Client

client = OpenAI(http_client=DefaultHttpx2Client())
async_client = AsyncOpenAI(http_client=DefaultAsyncHttpx2Client())
```

HTTPX remains installed, imported, and authoritative for the SDK's
public transport types. Existing HTTPX clients and helpers continue to
work unchanged. This is an explicit runtime opt-in, not a
default-transport or typing migration: generated/parsed API models
remain accurately typed, while _raw requests, responses, streams, and
transport exceptions can be HTTPX2 objects even where annotations still
describe HTTPX_.

The implementation keeps the compatibility boundary small:

- accepts sync, async, and module-level HTTPX2 clients and preserves
their native request/response/exception family;
- supplies `DefaultHttpx2Client` and `DefaultAsyncHttpx2Client` with the
SDK defaults, while retaining the existing HTTPX and aiohttp helpers;
- handles the concrete request, timeout, URL, response-casting, retry,
streaming, auth, and provider differences needed at runtime;
- makes workload-identity token exchange follow an explicitly selected
HTTPX2 client (including async clients, whose exchange still runs
synchronously in the existing worker-thread path), without changing
behavior merely because HTTPX2 happens to be installed.

The `httpx2` extra is available on Python 3.10+ and scopes its resolver
requirements to the opt-in path (`httpx>=0.25.1,<1`, `httpx2>=2.7,<3`,
and `anyio>=4.10,<5`). Base installations retain the existing Python,
HTTPX, and AnyIO floors. On Python 3.9 the extra is marker-skipped and
invoking an HTTPX2 helper produces an actionable error. Intentionally
mixed HTTPX/HTTPX2 transports or auth implementations are out of scope.

## Gaps
- This pr does not address `aiohttp`+ `httpx2`; that can be a future
addition if there is interest. This would require a separate addon and
for us to vendor in some of the implementation; so avoiding in this PR.
- Types stay on `httpx`, and `httpx` is still required to be an
installed package.
- reasoning: we considered `httpx | httpx2` as a migration shim, or
returning `httpx2` type annotations. Will continue to evaluate the
ecosystem, although `httpx2` types will unfortunately likely require
many updates from downstream packages. Saving those considerations for
potential future major versions.
- For now, we do not address `httpx.timeout` annotations in generated
code as well

## Testing

The HTTPX2 CI lane runs the normal suite with native HTTPX2 sync/async
clients in both Pydantic modes. Existing RESPX-backed cases are
exercised through a small, test-only bridge: HTTPX2 uses a native
`MockTransport`, the bridge translates only at the RESPX
matching/callback boundary, and the SDK still builds and receives native
HTTPX2 requests/responses. This keeps generated binary/raw/streaming,
retry, auth, Azure, Bedrock, and snapshot coverage shared instead of
maintaining duplicate tests; aiohttp remains a separate HTTPX-family
path.

Focused native tests also cover client defaults, direct injection,
raw/SSE/multipart, retries and exception families, hooks/mounts/proxies,
provider auth, workload-identity exchange/cache/401 refresh, and
base-only/extra resolver behavior. Local full-suite runs pass with
HTTPX2/Pydantic 2 and the HTTPX2 floor/Pydantic 1; the ordinary HTTPX
suite and lint/type checks remain clean.
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.

2 participants