feat(client): Add experimental runtime support for HTTPX2 clients - #3524
Conversation
There was a problem hiding this comment.
💡 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".
jbeckwith-oai
left a comment
There was a problem hiding this comment.
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:
- 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.
- 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
httpx2is 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).
jbeckwith-oai
left a comment
There was a problem hiding this comment.
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.
) 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.
Refs #3375.
Summary
Adds experimental runtime support for using an HTTPX2 client with the 2.x SDK:
pip install 'openai[httpx2]'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:
DefaultHttpx2ClientandDefaultAsyncHttpx2Clientwith the SDK defaults, while retaining the existing HTTPX and aiohttp helpers;The
httpx2extra 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, andanyio>=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
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.httpx, andhttpxis still required to be an installed package.httpx | httpx2as a migration shim, or returninghttpx2type annotations. Will continue to evaluate the ecosystem, althoughhttpx2types will unfortunately likely require many updates from downstream packages. Saving those considerations for potential future major versions.httpx.timeoutannotations in generated code as wellTesting
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.