Skip to content
Merged
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
2 changes: 1 addition & 1 deletion reflex/custom_components/custom_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from typing import Any

import click
import httpx

from reflex import constants
from reflex.constants import CustomComponents
Expand Down Expand Up @@ -467,6 +466,7 @@ def _collect_details_for_gallery():
Raises:
Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
"""
import httpx
from reflex_cli.utils import hosting

console.rule("[bold]Authentication with Reflex Services")
Expand Down
77 changes: 76 additions & 1 deletion reflex/utils/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import functools
from collections.abc import Callable
from typing import ParamSpec, TypeVar
from pathlib import Path
from typing import ParamSpec, TypeVar, cast

T = TypeVar("T")

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

return wrapper


def _write_cached_procedure_file(payload: str, cache_file: Path, value: object):
import pickle

cache_file.write_bytes(pickle.dumps((payload, value)))


def _read_cached_procedure_file(cache_file: Path) -> tuple[str | None, object]:
import pickle

if cache_file.exists():
with cache_file.open("rb") as f:
return pickle.loads(f.read())

return None, None


P = ParamSpec("P")
Picklable = TypeVar("Picklable")


def cached_procedure(
cache_file_path: Callable[[], Path],
payload_fn: Callable[P, str],
) -> Callable[[Callable[P, Picklable]], Callable[P, Picklable]]:
"""Decorator to cache the result of a function based on its arguments.

Args:
cache_file_path: Function that computes the cache file path.
payload_fn: Function that computes cache payload from function args.

Returns:
The decorated function.
"""

def _inner_decorator(func: Callable[P, Picklable]) -> Callable[P, Picklable]:
def _inner(*args: P.args, **kwargs: P.kwargs) -> Picklable:
_cache_file = cache_file_path()

payload, value = _read_cached_procedure_file(_cache_file)
new_payload = payload_fn(*args, **kwargs)

if payload != new_payload:
new_value = func(*args, **kwargs)
_write_cached_procedure_file(new_payload, _cache_file, new_value)
return new_value

from reflex.utils import console

console.debug(
f"Using cached value for {func.__name__} with payload: {new_payload}"
)
return cast("Picklable", value)

return _inner

return _inner_decorator


def cache_result_in_disk(
cache_file_path: Callable[[], Path],
) -> Callable[[Callable[[], Picklable]], Callable[[], Picklable]]:
"""Decorator to cache the result of a function on disk.

Args:
cache_file_path: Function that computes the cache file path.

Returns:
The decorated function.
"""
return cached_procedure(
cache_file_path=cache_file_path, payload_fn=lambda: "constant"
)
11 changes: 8 additions & 3 deletions reflex/utils/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from collections.abc import Callable
from typing import ParamSpec, TypeVar

import httpx

from reflex.utils.decorator import once
from reflex.utils.types import Unset

Expand Down Expand Up @@ -42,6 +40,8 @@ def _wrap_https_func(

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

url = args[0]
console.debug(f"Sending HTTPS request to {args[0]}")
initial_time = time.time()
Expand Down Expand Up @@ -94,6 +94,8 @@ def _is_ipv4_supported() -> bool:
Returns:
True if the system supports IPv4, False otherwise.
"""
import httpx

try:
httpx.head("http://1.1.1.1", timeout=3)
except httpx.RequestError:
Expand All @@ -108,6 +110,8 @@ def _is_ipv6_supported() -> bool:
Returns:
True if the system supports IPv6, False otherwise.
"""
import httpx

try:
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
except httpx.RequestError:
Expand Down Expand Up @@ -139,12 +143,13 @@ def _httpx_local_address_kwarg() -> str:


@once
def _httpx_client() -> httpx.Client:
def _httpx_client():
Comment thread
adhami3310 marked this conversation as resolved.
"""Get an HTTPX client.

Returns:
An HTTPX client.
"""
import httpx
from httpx._utils import get_environment_proxies

return httpx.Client(
Expand Down
74 changes: 8 additions & 66 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
import tempfile
import typing
import zipfile
from collections.abc import Callable, Sequence
from collections.abc import Sequence
from datetime import datetime
from pathlib import Path
from types import ModuleType
from typing import NamedTuple
from urllib.parse import urlparse

import click
import httpx
from alembic.util.exc import CommandError
from packaging import version
from redis import Redis as RedisSync
Expand All @@ -39,6 +38,7 @@
from reflex.config import Config, get_config
from reflex.environment import environment
from reflex.utils import console, net, path_ops, processes, redir
from reflex.utils.decorator import cached_procedure
from reflex.utils.exceptions import SystemPackageMissingError
from reflex.utils.misc import get_module_path
from reflex.utils.registry import get_npm_registry
Expand Down Expand Up @@ -1170,6 +1170,8 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
Raises:
Exit: If the script fails to download.
"""
import httpx

# Download the script
console.debug(f"Downloading {url}")
try:
Expand Down Expand Up @@ -1251,71 +1253,9 @@ def install_bun():
)


def _write_cached_procedure_file(payload: str, cache_file: str | Path):
cache_file = Path(cache_file)
cache_file.write_text(payload)


def _read_cached_procedure_file(cache_file: str | Path) -> str | None:
cache_file = Path(cache_file)
if cache_file.exists():
return cache_file.read_text()
return None


def _clear_cached_procedure_file(cache_file: str | Path):
cache_file = Path(cache_file)
if cache_file.exists():
cache_file.unlink()


def cached_procedure(
cache_file: str | None,
payload_fn: Callable[..., str],
cache_file_fn: Callable[[], str] | None = None,
):
"""Decorator to cache the runs of a procedure on disk. Procedures should not have
a return value.

Args:
cache_file: The file to store the cache payload in.
payload_fn: Function that computes cache payload from function args.
cache_file_fn: Function that computes the cache file name at runtime.

Returns:
The decorated function.

Raises:
ValueError: If both cache_file and cache_file_fn are provided.
"""
if cache_file and cache_file_fn is not None:
msg = "cache_file and cache_file_fn cannot both be provided."
raise ValueError(msg)

def _inner_decorator(func: Callable):
def _inner(*args, **kwargs):
_cache_file = cache_file_fn() if cache_file_fn is not None else cache_file
if not _cache_file:
msg = "Unknown cache file, cannot cache result."
raise ValueError(msg)
payload = _read_cached_procedure_file(_cache_file)
new_payload = payload_fn(*args, **kwargs)
if payload != new_payload:
_clear_cached_procedure_file(_cache_file)
func(*args, **kwargs)
_write_cached_procedure_file(new_payload, _cache_file)

return _inner

return _inner_decorator


@cached_procedure(
cache_file_fn=lambda: str(
get_web_dir() / "reflex.install_frontend_packages.cached"
),
payload_fn=lambda p, c: f"{sorted(p)!r},{c.json()}",
cache_file=None,
cache_file_path=lambda: get_web_dir() / "reflex.install_frontend_packages.cached",
payload_fn=lambda packages, config: f"{sorted(packages)!r},{config.json()}",
)
def install_frontend_packages(packages: set[str], config: Config):
"""Installs the base and custom frontend packages.
Expand Down Expand Up @@ -1725,6 +1665,8 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
Exit: If any download, file operations fail or unexpected zip file format.

"""
import httpx

# Create a temp directory for the zip download.
try:
temp_dir = tempfile.mkdtemp()
Expand Down
8 changes: 3 additions & 5 deletions reflex/utils/redir.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import time
import webbrowser

import httpx

from reflex import constants
from reflex.utils import net

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


def open_browser_and_wait(
target_url: str, poll_url: str, interval: int = 2
) -> httpx.Response:
def open_browser_and_wait(target_url: str, poll_url: str, interval: int = 2):
"""Open a browser window to target_url and request poll_url until it returns successfully.

Args:
Expand All @@ -38,6 +34,8 @@ def open_browser_and_wait(
Returns:
The response from the poll_url.
"""
import httpx

open_browser(target_url)
console.info("[b]Complete the workflow in the browser to continue.[/b]")
while True:
Expand Down
16 changes: 14 additions & 2 deletions reflex/utils/registry.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Utilities for working with registries."""

import httpx
from pathlib import Path

from reflex.environment import environment
from reflex.utils import console, net
from reflex.utils.decorator import once
from reflex.utils.decorator import cache_result_in_disk, once


def latency(registry: str) -> int:
Expand All @@ -16,6 +16,8 @@ def latency(registry: str) -> int:
Returns:
int: The latency of the registry in microseconds.
"""
import httpx

try:
time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
except httpx.HTTPError:
Expand All @@ -41,6 +43,16 @@ def average_latency(registry: str, attempts: int = 3) -> int:
return registry_latency


def _best_registry_file_path() -> Path:
"""Get the file path for the best registry cache.

Returns:
The file path for the best registry cache.
"""
return environment.REFLEX_DIR.get() / "reflex_best_registry.cached"


@cache_result_in_disk(cache_file_path=_best_registry_file_path)
def _get_best_registry() -> str:
"""Get the best registry based on latency.

Expand Down
4 changes: 2 additions & 2 deletions reflex/utils/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from datetime import datetime, timezone
from typing import TypedDict

import httpx

from reflex import constants
from reflex.environment import environment
from reflex.utils import console
Expand Down Expand Up @@ -218,6 +216,8 @@ def _prepare_event(event: str, **kwargs) -> _Event | None:


def _send_event(event_data: _Event) -> bool:
import httpx

try:
httpx.post(POSTHOG_API_URL, json=event_data)
except Exception:
Expand Down
12 changes: 9 additions & 3 deletions tests/units/test_prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ def test_update_react_router_config(config, export, expected_output):
def test_cached_procedure():
call_count = 0

@cached_procedure(tempfile.mktemp(), payload_fn=lambda: "constant")
temp_file = tempfile.mktemp()

@cached_procedure(
cache_file_path=lambda: Path(temp_file), payload_fn=lambda: "constant"
)
def _function_with_no_args():
nonlocal call_count
call_count += 1
Expand All @@ -74,8 +78,10 @@ def _function_with_no_args():

call_count = 0

another_temp_file = tempfile.mktemp()

@cached_procedure(
cache_file=tempfile.mktemp(),
cache_file_path=lambda: Path(another_temp_file),
payload_fn=lambda *args, **kwargs: f"{repr(args), repr(kwargs)}",
)
def _function_with_some_args(*args, **kwargs):
Expand All @@ -94,7 +100,7 @@ def _function_with_some_args(*args, **kwargs):
call_count = 0

@cached_procedure(
cache_file=None, cache_file_fn=tempfile.mktemp, payload_fn=lambda: "constant"
cache_file_path=lambda: Path(tempfile.mktemp()), payload_fn=lambda: "constant"
)
Comment thread
adhami3310 marked this conversation as resolved.
def _function_with_no_args_fn():
nonlocal call_count
Expand Down
Loading