Skip to content

Commit 60b580b

Browse files
authored
move httpx imports to function scope (#5612)
* move httpx imports to function scope * greptile is correct on this one * fix decorator message * there's no reason for _clear_cached_procedure_file to exist
1 parent 0c40fdd commit 60b580b

8 files changed

Lines changed: 121 additions & 83 deletions

File tree

reflex/custom_components/custom_components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from typing import Any
1313

1414
import click
15-
import httpx
1615

1716
from reflex import constants
1817
from reflex.constants import CustomComponents
@@ -467,6 +466,7 @@ def _collect_details_for_gallery():
467466
Raises:
468467
Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
469468
"""
469+
import httpx
470470
from reflex_cli.utils import hosting
471471

472472
console.rule("[bold]Authentication with Reflex Services")

reflex/utils/decorator.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import functools
44
from collections.abc import Callable
5-
from typing import ParamSpec, TypeVar
5+
from pathlib import Path
6+
from typing import ParamSpec, TypeVar, cast
67

78
T = TypeVar("T")
89

@@ -70,3 +71,77 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
7071
return result
7172

7273
return wrapper
74+
75+
76+
def _write_cached_procedure_file(payload: str, cache_file: Path, value: object):
77+
import pickle
78+
79+
cache_file.write_bytes(pickle.dumps((payload, value)))
80+
81+
82+
def _read_cached_procedure_file(cache_file: Path) -> tuple[str | None, object]:
83+
import pickle
84+
85+
if cache_file.exists():
86+
with cache_file.open("rb") as f:
87+
return pickle.loads(f.read())
88+
89+
return None, None
90+
91+
92+
P = ParamSpec("P")
93+
Picklable = TypeVar("Picklable")
94+
95+
96+
def cached_procedure(
97+
cache_file_path: Callable[[], Path],
98+
payload_fn: Callable[P, str],
99+
) -> Callable[[Callable[P, Picklable]], Callable[P, Picklable]]:
100+
"""Decorator to cache the result of a function based on its arguments.
101+
102+
Args:
103+
cache_file_path: Function that computes the cache file path.
104+
payload_fn: Function that computes cache payload from function args.
105+
106+
Returns:
107+
The decorated function.
108+
"""
109+
110+
def _inner_decorator(func: Callable[P, Picklable]) -> Callable[P, Picklable]:
111+
def _inner(*args: P.args, **kwargs: P.kwargs) -> Picklable:
112+
_cache_file = cache_file_path()
113+
114+
payload, value = _read_cached_procedure_file(_cache_file)
115+
new_payload = payload_fn(*args, **kwargs)
116+
117+
if payload != new_payload:
118+
new_value = func(*args, **kwargs)
119+
_write_cached_procedure_file(new_payload, _cache_file, new_value)
120+
return new_value
121+
122+
from reflex.utils import console
123+
124+
console.debug(
125+
f"Using cached value for {func.__name__} with payload: {new_payload}"
126+
)
127+
return cast("Picklable", value)
128+
129+
return _inner
130+
131+
return _inner_decorator
132+
133+
134+
def cache_result_in_disk(
135+
cache_file_path: Callable[[], Path],
136+
) -> Callable[[Callable[[], Picklable]], Callable[[], Picklable]]:
137+
"""Decorator to cache the result of a function on disk.
138+
139+
Args:
140+
cache_file_path: Function that computes the cache file path.
141+
142+
Returns:
143+
The decorated function.
144+
"""
145+
return cached_procedure(
146+
cache_file_path=cache_file_path, payload_fn=lambda: "constant"
147+
)

reflex/utils/net.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from collections.abc import Callable
66
from typing import ParamSpec, TypeVar
77

8-
import httpx
9-
108
from reflex.utils.decorator import once
119
from reflex.utils.types import Unset
1210

@@ -42,6 +40,8 @@ def _wrap_https_func(
4240

4341
@functools.wraps(func)
4442
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
43+
import httpx
44+
4545
url = args[0]
4646
console.debug(f"Sending HTTPS request to {args[0]}")
4747
initial_time = time.time()
@@ -94,6 +94,8 @@ def _is_ipv4_supported() -> bool:
9494
Returns:
9595
True if the system supports IPv4, False otherwise.
9696
"""
97+
import httpx
98+
9799
try:
98100
httpx.head("http://1.1.1.1", timeout=3)
99101
except httpx.RequestError:
@@ -108,6 +110,8 @@ def _is_ipv6_supported() -> bool:
108110
Returns:
109111
True if the system supports IPv6, False otherwise.
110112
"""
113+
import httpx
114+
111115
try:
112116
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
113117
except httpx.RequestError:
@@ -139,12 +143,13 @@ def _httpx_local_address_kwarg() -> str:
139143

140144

141145
@once
142-
def _httpx_client() -> httpx.Client:
146+
def _httpx_client():
143147
"""Get an HTTPX client.
144148
145149
Returns:
146150
An HTTPX client.
147151
"""
152+
import httpx
148153
from httpx._utils import get_environment_proxies
149154

150155
return httpx.Client(

reflex/utils/prerequisites.py

Lines changed: 8 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919
import tempfile
2020
import typing
2121
import zipfile
22-
from collections.abc import Callable, Sequence
22+
from collections.abc import Sequence
2323
from datetime import datetime
2424
from pathlib import Path
2525
from types import ModuleType
2626
from typing import NamedTuple
2727
from urllib.parse import urlparse
2828

2929
import click
30-
import httpx
3130
from alembic.util.exc import CommandError
3231
from packaging import version
3332
from redis import Redis as RedisSync
@@ -39,6 +38,7 @@
3938
from reflex.config import Config, get_config
4039
from reflex.environment import environment
4140
from reflex.utils import console, net, path_ops, processes, redir
41+
from reflex.utils.decorator import cached_procedure
4242
from reflex.utils.exceptions import SystemPackageMissingError
4343
from reflex.utils.misc import get_module_path
4444
from reflex.utils.registry import get_npm_registry
@@ -1170,6 +1170,8 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
11701170
Raises:
11711171
Exit: If the script fails to download.
11721172
"""
1173+
import httpx
1174+
11731175
# Download the script
11741176
console.debug(f"Downloading {url}")
11751177
try:
@@ -1251,71 +1253,9 @@ def install_bun():
12511253
)
12521254

12531255

1254-
def _write_cached_procedure_file(payload: str, cache_file: str | Path):
1255-
cache_file = Path(cache_file)
1256-
cache_file.write_text(payload)
1257-
1258-
1259-
def _read_cached_procedure_file(cache_file: str | Path) -> str | None:
1260-
cache_file = Path(cache_file)
1261-
if cache_file.exists():
1262-
return cache_file.read_text()
1263-
return None
1264-
1265-
1266-
def _clear_cached_procedure_file(cache_file: str | Path):
1267-
cache_file = Path(cache_file)
1268-
if cache_file.exists():
1269-
cache_file.unlink()
1270-
1271-
1272-
def cached_procedure(
1273-
cache_file: str | None,
1274-
payload_fn: Callable[..., str],
1275-
cache_file_fn: Callable[[], str] | None = None,
1276-
):
1277-
"""Decorator to cache the runs of a procedure on disk. Procedures should not have
1278-
a return value.
1279-
1280-
Args:
1281-
cache_file: The file to store the cache payload in.
1282-
payload_fn: Function that computes cache payload from function args.
1283-
cache_file_fn: Function that computes the cache file name at runtime.
1284-
1285-
Returns:
1286-
The decorated function.
1287-
1288-
Raises:
1289-
ValueError: If both cache_file and cache_file_fn are provided.
1290-
"""
1291-
if cache_file and cache_file_fn is not None:
1292-
msg = "cache_file and cache_file_fn cannot both be provided."
1293-
raise ValueError(msg)
1294-
1295-
def _inner_decorator(func: Callable):
1296-
def _inner(*args, **kwargs):
1297-
_cache_file = cache_file_fn() if cache_file_fn is not None else cache_file
1298-
if not _cache_file:
1299-
msg = "Unknown cache file, cannot cache result."
1300-
raise ValueError(msg)
1301-
payload = _read_cached_procedure_file(_cache_file)
1302-
new_payload = payload_fn(*args, **kwargs)
1303-
if payload != new_payload:
1304-
_clear_cached_procedure_file(_cache_file)
1305-
func(*args, **kwargs)
1306-
_write_cached_procedure_file(new_payload, _cache_file)
1307-
1308-
return _inner
1309-
1310-
return _inner_decorator
1311-
1312-
13131256
@cached_procedure(
1314-
cache_file_fn=lambda: str(
1315-
get_web_dir() / "reflex.install_frontend_packages.cached"
1316-
),
1317-
payload_fn=lambda p, c: f"{sorted(p)!r},{c.json()}",
1318-
cache_file=None,
1257+
cache_file_path=lambda: get_web_dir() / "reflex.install_frontend_packages.cached",
1258+
payload_fn=lambda packages, config: f"{sorted(packages)!r},{config.json()}",
13191259
)
13201260
def install_frontend_packages(packages: set[str], config: Config):
13211261
"""Installs the base and custom frontend packages.
@@ -1725,6 +1665,8 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
17251665
Exit: If any download, file operations fail or unexpected zip file format.
17261666
17271667
"""
1668+
import httpx
1669+
17281670
# Create a temp directory for the zip download.
17291671
try:
17301672
temp_dir = tempfile.mkdtemp()

reflex/utils/redir.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import time
44
import webbrowser
55

6-
import httpx
7-
86
from reflex import constants
97
from reflex.utils import net
108

@@ -25,9 +23,7 @@ def open_browser(target_url: str) -> None:
2523
console.info(f"Opening browser to {target_url}.")
2624

2725

28-
def open_browser_and_wait(
29-
target_url: str, poll_url: str, interval: int = 2
30-
) -> httpx.Response:
26+
def open_browser_and_wait(target_url: str, poll_url: str, interval: int = 2):
3127
"""Open a browser window to target_url and request poll_url until it returns successfully.
3228
3329
Args:
@@ -38,6 +34,8 @@ def open_browser_and_wait(
3834
Returns:
3935
The response from the poll_url.
4036
"""
37+
import httpx
38+
4139
open_browser(target_url)
4240
console.info("[b]Complete the workflow in the browser to continue.[/b]")
4341
while True:

reflex/utils/registry.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Utilities for working with registries."""
22

3-
import httpx
3+
from pathlib import Path
44

55
from reflex.environment import environment
66
from reflex.utils import console, net
7-
from reflex.utils.decorator import once
7+
from reflex.utils.decorator import cache_result_in_disk, once
88

99

1010
def latency(registry: str) -> int:
@@ -16,6 +16,8 @@ def latency(registry: str) -> int:
1616
Returns:
1717
int: The latency of the registry in microseconds.
1818
"""
19+
import httpx
20+
1921
try:
2022
time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
2123
except httpx.HTTPError:
@@ -41,6 +43,16 @@ def average_latency(registry: str, attempts: int = 3) -> int:
4143
return registry_latency
4244

4345

46+
def _best_registry_file_path() -> Path:
47+
"""Get the file path for the best registry cache.
48+
49+
Returns:
50+
The file path for the best registry cache.
51+
"""
52+
return environment.REFLEX_DIR.get() / "reflex_best_registry.cached"
53+
54+
55+
@cache_result_in_disk(cache_file_path=_best_registry_file_path)
4456
def _get_best_registry() -> str:
4557
"""Get the best registry based on latency.
4658

reflex/utils/telemetry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from datetime import datetime, timezone
1313
from typing import TypedDict
1414

15-
import httpx
16-
1715
from reflex import constants
1816
from reflex.environment import environment
1917
from reflex.utils import console
@@ -218,6 +216,8 @@ def _prepare_event(event: str, **kwargs) -> _Event | None:
218216

219217

220218
def _send_event(event_data: _Event) -> bool:
219+
import httpx
220+
221221
try:
222222
httpx.post(POSTHOG_API_URL, json=event_data)
223223
except Exception:

tests/units/test_prerequisites.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ def test_update_react_router_config(config, export, expected_output):
6262
def test_cached_procedure():
6363
call_count = 0
6464

65-
@cached_procedure(tempfile.mktemp(), payload_fn=lambda: "constant")
65+
temp_file = tempfile.mktemp()
66+
67+
@cached_procedure(
68+
cache_file_path=lambda: Path(temp_file), payload_fn=lambda: "constant"
69+
)
6670
def _function_with_no_args():
6771
nonlocal call_count
6872
call_count += 1
@@ -74,8 +78,10 @@ def _function_with_no_args():
7478

7579
call_count = 0
7680

81+
another_temp_file = tempfile.mktemp()
82+
7783
@cached_procedure(
78-
cache_file=tempfile.mktemp(),
84+
cache_file_path=lambda: Path(another_temp_file),
7985
payload_fn=lambda *args, **kwargs: f"{repr(args), repr(kwargs)}",
8086
)
8187
def _function_with_some_args(*args, **kwargs):
@@ -94,7 +100,7 @@ def _function_with_some_args(*args, **kwargs):
94100
call_count = 0
95101

96102
@cached_procedure(
97-
cache_file=None, cache_file_fn=tempfile.mktemp, payload_fn=lambda: "constant"
103+
cache_file_path=lambda: Path(tempfile.mktemp()), payload_fn=lambda: "constant"
98104
)
99105
def _function_with_no_args_fn():
100106
nonlocal call_count

0 commit comments

Comments
 (0)