[HTTPXodus] migrate httpx to httpx2 (dual import) - #7040
[HTTPXodus] migrate httpx to httpx2 (dual import)#7040ProgrammerPlus1998 wants to merge 2 commits into
Conversation
Greptile SummaryThe PR makes Reflex’s internal synchronous HTTP operations prefer
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| pyproject.toml | Adds the Python 3.10+ httpx2 dependency while retaining the existing httpx requirement. |
| reflex/utils/net.py | Prefers httpx2 for shared client creation, transport configuration, proxy discovery, and connectivity checks while preserving fallback behavior. |
| reflex/utils/telemetry.py | Routes synchronous telemetry posting through the preferred httpx2 binding with an httpx fallback. |
| reflex/utils/templates.py | Updates remote-template error handling to use the same dual-import HTTP binding. |
| reflex/utils/js_runtimes.py | Updates runtime downloads to use the preferred httpx2 binding with fallback. |
| reflex/utils/frontend_skeleton.py | Updates AGENTS.md content fetching and its exception handling to use the dual-import binding. |
| reflex/custom_components/custom_components.py | Updates custom-component gallery requests to use the preferred HTTP implementation. |
| tests/units/test_telemetry.py | Patches the selected HTTP module’s post function so telemetry tests follow production import selection. |
| tests/units/utils/test_utils.py | Uses the selected HTTP module’s exception class when testing failed frontend-skeleton fetches. |
| uv.lock | Records httpx2 and associated resolver output for the updated dependency set. |
Reviews (2): Last reviewed commit: "fix(reflex): suppress pyright min-versio..." | 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>
Merging this PR will improve performance by 4.63%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_collect_imports[_complicated_page] |
1.6 ms | 1.6 ms | +4.63% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing ProgrammerPlus1998:httpxodus/httpx2-migration (b965d6f) with main (3e3732d)
Footnotes
-
8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
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
…x dual-import Signed-off-by: xic <xiechen@cls.cn>
b965d6f to
471b42b
Compare
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. 🙏