Skip to content

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

Open
ProgrammerPlus1998 wants to merge 1 commit into
reflex-dev:mainfrom
ProgrammerPlus1998:httpxodus/httpx2-migration
Open

[HTTPXodus] migrate httpx to httpx2 (dual import)#7040
ProgrammerPlus1998 wants to merge 1 commit 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. 🙏

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
@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 adds httpx2 as a runtime dependency and updates internal synchronous HTTP call sites to prefer it while retaining an httpx fallback.

  • Adds httpx2 and its transitive dependencies to project metadata and the lockfile.
  • Applies dual imports across networking, telemetry, registry, template, runtime-download, and custom-component workflows.
  • Updates affected unit-test mocks and exception types to follow the selected implementation.
  • Documents the migration strategy, validation, and TLS trust-store difference.

Confidence Score: 5/5

The 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.

Important Files Changed

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

@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>

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