[HTTPXodus] migrate httpx to httpx2 (hard switch) - #7040
[HTTPXodus] migrate httpx to httpx2 (hard switch)#7040ProgrammerPlus1998 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 12 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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.
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
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="reflex/utils/net.py">
<violation number="1" location="reflex/utils/net.py:159">
P3: The comment block just above this import still describes behavior removed by this change: it says the code "fall[s] back to real httpx on Python 3.8/3.9" and that `httpx2` is "a union of the two modules" via a try/except. This file now hard-imports httpx2, and `pyproject.toml` declares `httpx2 >=2.0` for the full supported range (`requires-python = ">=3.10"`), so there is no fallback and no union. The stale text and the `# type: ignore` suppressions (added only to silence the union inference) will mislead future maintainers. Update the comments to state that httpx2 is the sole HTTP client, and drop the now-unneeded `# type: ignore` suppressions on the Client/HTTPTransport/Proxy calls now that `httpx2` is no longer a union.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| # infer a union of `httpx2.HTTPTransport | httpx.HTTPTransport` — | ||
| # that union is not assignable to `Client(mounts=...)` because the | ||
| # two transport classes are unrelated. | ||
| import httpx2 |
There was a problem hiding this comment.
P3: The comment block just above this import still describes behavior removed by this change: it says the code "fall[s] back to real httpx on Python 3.8/3.9" and that httpx2 is "a union of the two modules" via a try/except. This file now hard-imports httpx2, and pyproject.toml declares httpx2 >=2.0 for the full supported range (requires-python = ">=3.10"), so there is no fallback and no union. The stale text and the # type: ignore suppressions (added only to silence the union inference) will mislead future maintainers. Update the comments to state that httpx2 is the sole HTTP client, and drop the now-unneeded # type: ignore suppressions on the Client/HTTPTransport/Proxy calls now that httpx2 is no longer a union.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/utils/net.py, line 159:
<comment>The comment block just above this import still describes behavior removed by this change: it says the code "fall[s] back to real httpx on Python 3.8/3.9" and that `httpx2` is "a union of the two modules" via a try/except. This file now hard-imports httpx2, and `pyproject.toml` declares `httpx2 >=2.0` for the full supported range (`requires-python = ">=3.10"`), so there is no fallback and no union. The stale text and the `# type: ignore` suppressions (added only to silence the union inference) will mislead future maintainers. Update the comments to state that httpx2 is the sole HTTP client, and drop the now-unneeded `# type: ignore` suppressions on the Client/HTTPTransport/Proxy calls now that `httpx2` is no longer a union.</comment>
<file context>
@@ -165,12 +156,8 @@ def _httpx_client():
- except ModuleNotFoundError:
- import httpx as httpx2 # noqa: F401 — local name `httpx2` bound to the real httpx
- from httpx._utils import get_environment_proxies # noqa: F811
+ import httpx2
+ from httpx2._utils import get_environment_proxies
</file context>
Use direct 'import httpx2' across the main package: drop the 'import httpx2 as httpx' aliasing and the two remaining ModuleNotFoundError fallbacks in tests. Remove the httpx runtime dependency; httpx2>=2.12.0 is unconditional (requires-python is already >=3.10). Repair uv.lock: replace a corrupt duplicate 'ty' entry (0.0.69 pointing at truststore artifacts, rejected by uv) and record the httpx2 swap. reflex-hosting-cli (separate distribution, pins httpx <1.0) and reflex-site-shared keep httpx for now; httpx remains installed transitively via reflex-hosting-cli. Refs: reflex-dev#7034
fb48705 to
e71e2e5
Compare
| testing = [ | ||
| { name = "psutil" }, | ||
| { name = "selenium" }, | ||
| { name = "uvicorn" }, | ||
| ] |
There was a problem hiding this comment.
Lockfile Adds Undeclared Extra
The regenerated lockfile adds a testing extra with psutil, selenium, and uvicorn, but pyproject.toml defines only the db and pydantic extras. This leaves the lockfile out of sync with the project manifest and can cause checked or frozen dependency workflows to fail. Please regenerate it strictly from the committed manifest without the unrelated extra.
| "click >=8.2", | ||
| "granian[reload] >=2.7.4", | ||
| "httpx >=0.26,<1.0", | ||
| "httpx2 >=2.12.0", |
There was a problem hiding this comment.
Required News Fragment Missing
This hard switch changes the runtime HTTP dependency and TLS trust behavior for downstream users, but it does not add a root-package news fragment. The repository requires user-facing changes to include a news/7040.<type>.md entry explaining what changed and what it means for users, so this requirement must be satisfied before merging.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Closes #7034
What this PR does
Complete hard switch of the main
reflexpackage fromhttpxtohttpx2:import httpx2everywhere — notry/except ImportErrorfallback, noimport httpx2 as httpxaliasinghttpxruntime dependency;httpx2>=2.12.0unconditional (requires-pythonis already>=3.10, so no version-marker needed)httpx.Client,httpx.AsyncClient,httpx.Limits,httpx.HTTPTransport,httpx.Proxy,httpx.HTTPError→httpx2.Xfrom httpx._utils import get_environment_proxies→from httpx2._utils import get_environment_proxies(httpx2 ships the same helper)Diff summary
12 files (commit
e71e2e5):reflex/utils/(net, telemetry, registry, js_runtimes, templates, frontend_skeleton)import httpx2 as httpx→import httpx2; all usages renamedreflex/custom_components/custom_components.pytests/units/test_telemetry.py,tests/units/utils/test_utils.py,tests/test_node_version.pyModuleNotFoundErrorfallbacks; patch targets and mock dependency data updatedpyproject.tomlhttpx >=0.26,<1.0;httpx2 >=2.12.0without version markeruv.locktyentry (0.0.69 pointing at truststore artifacts) that current uv releases refuse to parseScope
packages/reflex-hosting-cli(a separate distribution that pinshttpx <1.0) andpackages/reflex-site-sharedare unchanged — they keep httpx for now, so httpx remains installed transitively viareflex-hosting-cli. Migrating the hosting CLI (50+ lazy imports inhosting.pyalone) is a natural follow-up PR.Test results
pytest tests/units: 7997 passed, 18 skipped, in a fresh venv installed from this branch'suv.lock(uv sync --frozen) with httpx2 2.12.0tests/units/reflex_cli/(the hosting-CLI sub-package this PR deliberately does not touch): they fail with "Reflex version 0.0.0.post2.dev0 is not compatible with reflex-hosting-cli", an artifact of the local editable install having no git tags (uv-dynamic-versioning fallback), not of this changeNotes for reviewer
httpx2verifies TLS against the OS trust store instead of the bundledcertifi. Self-hosted Reflex deployments behind corporate proxies or in minimal containers that relied on certifi's CA bundle may needSSL_CERT_FILE/SSL_CERT_DIRafter the switch. Worth a line in the changelog.uv.lockrepair (corrupttyentry) is included here because the dependency swap requires re-locking anyway; happy to split it out if you prefer.Happy to revise per review — and equally happy to close this PR if the maintainers would rather wait for
httpx1.0 stable. 🙏