Skip to content

[HTTPXodus] migrate httpx to httpx2 (dual import) - #7040

Open
ProgrammerPlus1998 wants to merge 2 commits into
reflex-dev:mainfrom
ProgrammerPlus1998:httpxodus/httpx2-migration
Open

[HTTPXodus] migrate httpx to httpx2 (dual import)#7040
ProgrammerPlus1998 wants to merge 2 commits into
reflex-dev:mainfrom
ProgrammerPlus1998:httpxodus/httpx2-migration

Conversation

@ProgrammerPlus1998

@ProgrammerPlus1998 ProgrammerPlus1998 commented Sep 3, 2026

Copy link
Copy Markdown

Closes #7034

🏷️ Part of HTTPXodus — a community effort to help major Python projects plan their path off the stalled httpx stable line onto httpx2, the actively maintained fork by Pydantic Services. One coordinated PR per project — no drive-by changes.

What this PR does

Switches the httpx calls inside Reflex's internal framework / CLI tooling from httpx to httpx2 using a dual import. User app code is unaffected — it never imports httpx directly; user apps are served over ASGI via Starlette / Granian, which has its own httpx / httpx2 line. reflex already requires python>=3.10,<4.0, which is exactly the floor that httpx2 requires, 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 use try: import httpx2 as httpx; except ImportError: import httpx. All 7 call sites are synchronous (no AsyncClient anywhere in the package) and most are lazily imported inside functions.

reflex/utils/net.py is the only place that pokes at a private module: from httpx._utils import get_environment_proxies. httpx2 ships an equivalent helper in src/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.py and one other) needed the same dual import because they use httpx.ConnectError / httpx.post as side_effect values for monkeypatching — the SUT now uses httpx2.ConnectError / httpx2.post, so the test-side identifiers had to match.

pyproject.toml adds httpx2>=2.0; python_version >= "3.10" next to the existing httpx >=0.26,<1.0 (kept so environments that lack a Python-version marker resolver still install).

Test results

Validated in a fresh uv environment (reflex uses uv / hatch, not poetry):

  • uv sync — exit 0; both httpx-0.28.1 and httpx2-2.12.0 installed.
  • pytest reflex/utils tests/units/8137 passed, 18 skipped, 0 failed.
  • ruff check . and ruff format --check — clean.

Notes for reviewer

  • reflex-hosting-cli (a separate distribution that Reflex depends on) independently pins httpx >=0.25.1,<1.0. Out of scope for this PR but a complete migration story would need a follow-up there. reflex-base has no httpx dependency.
  • ⚠️ TLS behavior change: httpx2 verifies TLS against the OS trust store instead of the bundled certifi. Reflex already has first-class proxy / verify= handling in net.py for exactly the corporate-proxy / locked-down-container users this change affects — those environments may need SSL_CERT_FILE / system CA configuration after the switch. Worth a line in the changelog.
  • The from httpx._utils import get_environment_proxies import is a private API. Both httpx and httpx2 ship the helper today, but the dual-import pattern here means we now depend on httpx2._utils having 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 httpx 1.0 stable. 🙏

@ProgrammerPlus1998
ProgrammerPlus1998 requested a review from a team as a code owner September 3, 2026 04:14
@greptile-apps

greptile-apps Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes Reflex’s internal synchronous HTTP operations prefer httpx2 while retaining httpx as a fallback.

  • Adds httpx2 to project and lockfile dependencies.
  • Updates network, telemetry, runtime-download, template, registry, and custom-component utilities to use dual imports.
  • Updates affected unit-test mocks and exception types to follow the selected HTTP implementation.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

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

@cubic-dev-ai cubic-dev-ai Bot 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.

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

Comment thread pyproject.toml
"click >=8.2",
"granian[reload] >=2.7.4",
"httpx >=0.26,<1.0",
"httpx2 >=2.0; python_version >= '3.10'",

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.

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

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.

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>

@codspeed-hq

codspeed-hq Bot commented Sep 3, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 4.63%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 31 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

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)

Open in CodSpeed

Footnotes

  1. 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>
@ProgrammerPlus1998
ProgrammerPlus1998 force-pushed the httpxodus/httpx2-migration branch from b965d6f to 471b42b Compare September 4, 2026 02:26
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.

[HTTPXodus] Consider migrating from httpx to httpx2 (the actively maintained fork)

1 participant