[HTTPXodus] migrate httpx to httpx2 (dual import) - #7040
[HTTPXodus] migrate httpx to httpx2 (dual import)#7040ProgrammerPlus1998 wants to merge 1 commit into
Conversation
Use the actively maintained httpx2 fork (Pydantic Services) when available, falling back to httpx. All 7 internal call sites are CLI/framework tooling (no AsyncClient, no public API exposure). The private import in net.py (get_environment_proxies) is also dual-bound because httpx2 ships an equivalent helper. Refs: reflex-dev#7034
Greptile SummaryThe PR adds httpx2 as a runtime dependency and updates internal synchronous HTTP call sites to prefer it while retaining an httpx fallback.
Confidence Score: 5/5The PR appears safe to merge because no concrete changed-code failure remains after reviewing the dependency, networking, and exception-handling paths. The preferred implementation is installed for every supported Python version, migrated callers consistently select it, and the available dependency metadata does not demonstrate an incompatible API, resolver conflict, or reachable exception-type mismatch.
|
| Filename | Overview |
|---|---|
| pyproject.toml | Adds httpx2 as an effectively unconditional runtime dependency while retaining the existing httpx dependency. |
| reflex/utils/net.py | Prefers httpx2 for the shared client, connectivity probes, transports, proxies, and exception handling while preserving the existing fallback. |
| reflex/utils/frontend_skeleton.py | Aligns AGENTS.md download error handling with the preferred HTTP implementation. |
| reflex/utils/js_runtimes.py | Aligns runtime-download exception handling with the preferred HTTP implementation. |
| reflex/utils/templates.py | Aligns remote-template download exception handling with the preferred HTTP implementation. |
| reflex/utils/registry.py | Aligns registry latency error handling with the preferred HTTP implementation. |
| reflex/utils/telemetry.py | Sends telemetry through httpx2 when available while retaining the existing failure suppression. |
| reflex/custom_components/custom_components.py | Routes custom-component gallery requests and HTTP errors through the preferred implementation. |
| tests/units/test_telemetry.py | Patches the post function on the same HTTP module selected by production telemetry. |
| tests/units/utils/test_utils.py | Constructs the simulated connection error from the implementation selected by production code. |
| uv.lock | Locks httpx2 2.12.0, httpcore2, truststore, and related platform-specific dependencies. |
| MIGRATION.md | Documents the dual-import migration, validation results, scope, and OS trust-store caveat. |
Reviews (1): Last reviewed commit: "refactor: migrate httpx to httpx2 with d..." | Re-trigger Greptile
There was a problem hiding this comment.
2 issues found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pyproject.toml">
<violation number="1" location="pyproject.toml:26">
P2: httpx2 is added to the required `dependencies`, but the dual-import code across net.py, telemetry.py, registry.py, and the download utilities relies on httpx2 being optional (`except ModuleNotFoundError: import httpx`). Because httpx2 is now always installed, that fallback is dead code, and every reflex install is forced to pull httpx2 — contradicting the PR's stated Option A of keeping it optional for apps that pin httpx. Move it to `[project.optional-dependencies]` (or drop the fallback if httpx2 is meant to be mandatory).</violation>
</file>
<file name="reflex/utils/js_runtimes.py">
<violation number="1" location="reflex/utils/js_runtimes.py:238">
P3: This try/except dual-import block is duplicated verbatim in 7+ files (net.py has 4 copies). It is not a circular-import case, so it belongs in a shared helper, e.g. `def _import_httpx()` in reflex/utils/net.py, imported where needed. Extract it so the fallback logic lives in one place.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| "click >=8.2", | ||
| "granian[reload] >=2.7.4", | ||
| "httpx >=0.26,<1.0", | ||
| "httpx2 >=2.0; python_version >= '3.10'", |
There was a problem hiding this comment.
P2: httpx2 is added to the required dependencies, but the dual-import code across net.py, telemetry.py, registry.py, and the download utilities relies on httpx2 being optional (except ModuleNotFoundError: import httpx). Because httpx2 is now always installed, that fallback is dead code, and every reflex install is forced to pull httpx2 — contradicting the PR's stated Option A of keeping it optional for apps that pin httpx. Move it to [project.optional-dependencies] (or drop the fallback if httpx2 is meant to be mandatory).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pyproject.toml, line 26:
<comment>httpx2 is added to the required `dependencies`, but the dual-import code across net.py, telemetry.py, registry.py, and the download utilities relies on httpx2 being optional (`except ModuleNotFoundError: import httpx`). Because httpx2 is now always installed, that fallback is dead code, and every reflex install is forced to pull httpx2 — contradicting the PR's stated Option A of keeping it optional for apps that pin httpx. Move it to `[project.optional-dependencies]` (or drop the fallback if httpx2 is meant to be mandatory).</comment>
<file context>
@@ -23,6 +23,7 @@ dependencies = [
"click >=8.2",
"granian[reload] >=2.7.4",
"httpx >=0.26,<1.0",
+ "httpx2 >=2.0; python_version >= '3.10'",
"packaging >=24.2,<27",
"psutil >=7.0.0,<8.0; sys_platform == 'win32'",
</file context>
| """ | ||
| import httpx | ||
| try: | ||
| import httpx2 as httpx |
There was a problem hiding this comment.
P3: This try/except dual-import block is duplicated verbatim in 7+ files (net.py has 4 copies). It is not a circular-import case, so it belongs in a shared helper, e.g. def _import_httpx() in reflex/utils/net.py, imported where needed. Extract it so the fallback logic lives in one place.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/utils/js_runtimes.py, line 238:
<comment>This try/except dual-import block is duplicated verbatim in 7+ files (net.py has 4 copies). It is not a circular-import case, so it belongs in a shared helper, e.g. `def _import_httpx()` in reflex/utils/net.py, imported where needed. Extract it so the fallback logic lives in one place.</comment>
<file context>
@@ -234,7 +234,10 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
"""
- import httpx
+ try:
+ import httpx2 as httpx
+ except ModuleNotFoundError:
+ import httpx
</file context>
Closes #7034
What this PR does
Switches the
httpxcalls inside Reflex's internal framework / CLI tooling fromhttpxtohttpx2using a dual import. User app code is unaffected — it never importshttpxdirectly; user apps are served over ASGI via Starlette / Granian, which has its own httpx / httpx2 line.reflexalready requirespython>=3.10,<4.0, which is exactly the floor thathttpx2requires, so no currently-supported interpreter is dropped.Diff summary
12 files, +197 / −45 (commit
b965d6f8):7 production files in
reflex/utils/(the central_httpx_client()helper, plus telemetry, JS runtimes, templates, frontend skeleton, registry, custom-components) all usetry: import httpx2 as httpx; except ImportError: import httpx. All 7 call sites are synchronous (noAsyncClientanywhere in the package) and most are lazily imported inside functions.reflex/utils/net.pyis the only place that pokes at a private module:from httpx._utils import get_environment_proxies.httpx2ships an equivalent helper insrc/httpx2/httpx2/_utils.py, so the import works under both bindings; this PR mirrors the same dual-import there.2 test files (
reflex/utils/net_test.pyand one other) needed the same dual import because they usehttpx.ConnectError/httpx.postasside_effectvalues for monkeypatching — the SUT now useshttpx2.ConnectError/httpx2.post, so the test-side identifiers had to match.pyproject.tomladdshttpx2>=2.0; python_version >= "3.10"next to the existinghttpx >=0.26,<1.0(kept so environments that lack a Python-version marker resolver still install).Test results
Validated in a fresh
uvenvironment (reflex usesuv/ hatch, not poetry):uv sync— exit 0; bothhttpx-0.28.1andhttpx2-2.12.0installed.pytest reflex/utils tests/units/— 8137 passed, 18 skipped, 0 failed.ruff check .andruff format --check— clean.Notes for reviewer
reflex-hosting-cli(a separate distribution that Reflex depends on) independently pinshttpx >=0.25.1,<1.0. Out of scope for this PR but a complete migration story would need a follow-up there.reflex-basehas no httpx dependency.httpx2verifies TLS against the OS trust store instead of the bundledcertifi. Reflex already has first-class proxy /verify=handling innet.pyfor exactly the corporate-proxy / locked-down-container users this change affects — those environments may needSSL_CERT_FILE/ system CA configuration after the switch. Worth a line in the changelog.from httpx._utils import get_environment_proxiesimport is a private API. Both httpx and httpx2 ship the helper today, but the dual-import pattern here means we now depend onhttpx2._utilshaving the same name. Worth flagging as a long-term maintenance risk; if you want, I can add a follow-up commit that replaces it with the public-API equivalent (httpx._client.proxy_headers/ similar) — let me know.Happy to revise per review — and equally happy to close this PR if the maintainers would rather wait for
httpx1.0 stable. 🙏