migrate httpx to httpx2 (dual import) - #7040
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
masenf
left a comment
There was a problem hiding this comment.
i dont understand how the "dual import" thing really helps us here.
if we're adding httpx2 to the framework deps, it will always be installed and thus we should just import httpx2.
if downstream code was depending on httpx and not declaring it as their own dependency, then they will be broken, but i'm okay with that. downstream should not be assuming that the framework dependencies can just be used because they happen to be there.
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. 🙏