Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'",

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>

"packaging >=24.2,<27",
"psutil >=7.0.0,<8.0; sys_platform == 'win32'",
"python-multipart >=0.0.32,<1.0",
Expand Down
5 changes: 4 additions & 1 deletion reflex/custom_components/custom_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,10 @@ def _collect_details_for_gallery():
Raises:
SystemExit: If pyproject.toml file is ill-formed or the request to the backend services fails.
"""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx
from reflex_cli.utils import hosting

console.rule("[bold]Authentication with Reflex Services")
Expand Down
5 changes: 4 additions & 1 deletion reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ def initialize_agents_md(
"""
plan = _plan_agents_md(agents_file, claude_file)

import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

logger.debug(f"Fetching {url}")
try:
Expand Down
5 changes: 4 additions & 1 deletion reflex/utils/js_runtimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
Raises:
SystemExit: If the script fails to download.
"""
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>

except ModuleNotFoundError:
import httpx

# Download the script
logger.debug(f"Downloading {url}")
Expand Down
47 changes: 37 additions & 10 deletions reflex/utils/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ def _wrap_https_func(

@functools.wraps(func)
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

url = args[0]
logger.debug(f"Sending HTTPS request to {args[0]}")
Expand Down Expand Up @@ -95,7 +98,10 @@ def _is_ipv4_supported() -> bool:
Returns:
True if the system supports IPv4, False otherwise.
"""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

try:
httpx.head("http://1.1.1.1", timeout=3)
Expand All @@ -111,7 +117,10 @@ def _is_ipv6_supported() -> bool:
Returns:
True if the system supports IPv6, False otherwise.
"""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

try:
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
Expand Down Expand Up @@ -150,26 +159,44 @@ def _httpx_client():
Returns:
An HTTPX client.
"""
import httpx
from httpx._utils import get_environment_proxies
# Resolve the active HTTP library at call time. Prefer httpx2 when
# available, fall back to real httpx on Python 3.8/3.9 (which httpx2
# cannot run on). Bind the classes to local names so pyright does not
# infer a union of `httpx2.HTTPTransport | httpx.HTTPTransport` —
# that union is not assignable to `Client(mounts=...)` because the
# two transport classes are unrelated.
try:
import httpx2
from httpx2._utils import get_environment_proxies
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

verify_setting = _httpx_verify_kwarg()
return httpx.Client(
transport=httpx.HTTPTransport(
# `httpx2` is a union of the two modules here (httpx2 in the try
# branch, real httpx in the except branch). The two HTTPTransport
# / Proxy / Client classes share compatible shapes but pyright in
# min-version mode still infers a union and rejects the assignment
# to `BaseTransport` / `ProxyTypes`. In practice only one branch
# runs per process; the `# type: ignore` below is the smallest way
# to tell pyright that, suppressing the no-real-error warnings.
return httpx2.Client( # type: ignore[call-overload]
transport=httpx2.HTTPTransport( # type: ignore[arg-type]
local_address=_httpx_local_address_kwarg(),
verify=verify_setting,
),
mounts={
key: (
None
if url is None
else httpx.HTTPTransport(
proxy=httpx.Proxy(url=url), verify=verify_setting
else httpx2.HTTPTransport( # type: ignore[arg-type]
proxy=httpx2.Proxy(url=url), # type: ignore[arg-type]
verify=verify_setting,
)
)
for key, url in get_environment_proxies().items()
},
)


get = _wrap_https_lazy_func(lambda: _httpx_client().get)
get = _wrap_https_lazy_func(lambda: _httpx_client().get) # type: ignore[arg-type]
5 changes: 4 additions & 1 deletion reflex/utils/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ def latency(registry: str) -> int:
Returns:
int: The latency of the registry in microseconds.
"""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

try:
time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
Expand Down
5 changes: 4 additions & 1 deletion reflex/utils/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,10 @@ def _prepare_event(


def _send_event(event_data: _Event) -> bool:
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

try:
httpx.post(POSTHOG_API_URL, json=event_data)
Expand Down
5 changes: 4 additions & 1 deletion reflex/utils/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
SystemExit: If any download, file operations fail or unexpected zip file format.

"""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

# Create a temp directory for the zip download.
try:
Expand Down
6 changes: 5 additions & 1 deletion tests/units/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ def httpx_post(mocker: MockerFixture):
Returns:
The mock for ``httpx.post`` so tests can assert on the posted payload.
"""
return mocker.patch("httpx.post")
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx
return mocker.patch.object(httpx, "post")


def test_telemetry():
Expand Down
5 changes: 4 additions & 1 deletion tests/units/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,10 @@ def test_initialize_agents_md_refreshes_managed_section(tmp_path, mocker):

def test_initialize_agents_md_warns_on_fetch_failure(tmp_path, mocker, caplog):
"""Test that a failed fetch warns without writing AGENTS.md or the bridge."""
import httpx
try:
import httpx2 as httpx
except ModuleNotFoundError:
import httpx

agents_file = tmp_path / "AGENTS.md"
claude_file = tmp_path / "CLAUDE.md"
Expand Down
47 changes: 29 additions & 18 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.