Skip to content

Commit d1ac42e

Browse files
committed
Merge branch 'main' into release/reflex-0.7.4
2 parents e1a7788 + 2bc3499 commit d1ac42e

File tree

8 files changed

+184
-31
lines changed

8 files changed

+184
-31
lines changed

reflex/app.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,8 +1108,6 @@ def get_compilation_time() -> str:
11081108
if config.react_strict_mode:
11091109
app_wrappers[(200, "StrictMode")] = StrictMode.create()
11101110

1111-
should_compile = self._should_compile()
1112-
11131111
if not should_compile:
11141112
with console.timing("Evaluate Pages (Backend)"):
11151113
for route in self._unevaluated_pages:

reflex/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,9 @@ class EnvironmentVariables:
720720
# Used by flexgen to enumerate the pages.
721721
REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
722722

723+
# The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
724+
REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
725+
723726

724727
environment = EnvironmentVariables()
725728

reflex/state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ def get_parent_state(cls) -> Type[BaseState] | None:
907907
raise ValueError(f"Only one parent state is allowed {parent_states}.")
908908
# The first non-mixin state in the mro is our parent.
909909
for base in cls.mro()[1:]:
910-
if base._mixin or not issubclass(base, BaseState):
910+
if not issubclass(base, BaseState) or base._mixin:
911911
continue
912912
if base is BaseState:
913913
break

reflex/utils/net.py

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
"""Helpers for downloading files from the network."""
22

3+
import functools
4+
import time
5+
from typing import Callable, ParamSpec, TypeVar
6+
37
import httpx
48

5-
from ..config import environment
9+
from reflex.utils.decorator import once
10+
611
from . import console
712

813

@@ -12,30 +17,114 @@ def _httpx_verify_kwarg() -> bool:
1217
Returns:
1318
True if SSL verification is enabled, False otherwise
1419
"""
20+
from ..config import environment
21+
1522
return not environment.SSL_NO_VERIFY.get()
1623

1724

18-
def get(url: str, **kwargs) -> httpx.Response:
19-
"""Make an HTTP GET request.
25+
_P = ParamSpec("_P")
26+
_T = TypeVar("_T")
27+
28+
29+
def _wrap_https_func(
30+
func: Callable[_P, _T],
31+
) -> Callable[_P, _T]:
32+
"""Wrap an HTTPS function with logging.
2033
2134
Args:
22-
url: The URL to request.
23-
**kwargs: Additional keyword arguments to pass to httpx.get.
35+
func: The function to wrap.
2436
2537
Returns:
26-
The response object.
38+
The wrapped function.
39+
"""
40+
41+
@functools.wraps(func)
42+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
43+
url = args[0]
44+
console.debug(f"Sending HTTPS request to {args[0]}")
45+
initial_time = time.time()
46+
try:
47+
response = func(*args, **kwargs)
48+
except httpx.ConnectError as err:
49+
if "CERTIFICATE_VERIFY_FAILED" in str(err):
50+
# If the error is a certificate verification error, recommend mitigating steps.
51+
console.error(
52+
f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
53+
"path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
54+
)
55+
raise
56+
else:
57+
console.debug(
58+
f"Received response from {url} in {time.time() - initial_time:.3f} seconds"
59+
)
60+
return response
61+
62+
return wrapper
63+
2764

28-
Raises:
29-
httpx.ConnectError: If the connection cannot be established.
65+
def _is_ipv4_supported() -> bool:
66+
"""Determine if the system supports IPv4.
67+
68+
Returns:
69+
True if the system supports IPv4, False otherwise.
3070
"""
31-
kwargs.setdefault("verify", _httpx_verify_kwarg())
3271
try:
33-
return httpx.get(url, **kwargs)
34-
except httpx.ConnectError as err:
35-
if "CERTIFICATE_VERIFY_FAILED" in str(err):
36-
# If the error is a certificate verification error, recommend mitigating steps.
37-
console.error(
38-
f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
39-
"path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
40-
)
41-
raise
72+
httpx.head("http://1.1.1.1", timeout=3)
73+
except httpx.RequestError:
74+
return False
75+
else:
76+
return True
77+
78+
79+
def _is_ipv6_supported() -> bool:
80+
"""Determine if the system supports IPv6.
81+
82+
Returns:
83+
True if the system supports IPv6, False otherwise.
84+
"""
85+
try:
86+
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
87+
except httpx.RequestError:
88+
return False
89+
else:
90+
return True
91+
92+
93+
def _should_use_ipv6() -> bool:
94+
"""Determine if the system supports IPv6.
95+
96+
Returns:
97+
True if the system supports IPv6, False otherwise.
98+
"""
99+
return not _is_ipv4_supported() and _is_ipv6_supported()
100+
101+
102+
def _httpx_local_address_kwarg() -> str:
103+
"""Get the value of the HTTPX local_address keyword argument.
104+
105+
Returns:
106+
The local address to bind to
107+
"""
108+
from ..config import environment
109+
110+
return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or (
111+
"::" if _should_use_ipv6() else "0.0.0.0"
112+
)
113+
114+
115+
@once
116+
def _httpx_client() -> httpx.Client:
117+
"""Get an HTTPX client.
118+
119+
Returns:
120+
An HTTPX client.
121+
"""
122+
return httpx.Client(
123+
transport=httpx.HTTPTransport(
124+
local_address=_httpx_local_address_kwarg(),
125+
verify=_httpx_verify_kwarg(),
126+
)
127+
)
128+
129+
130+
get = _wrap_https_func(_httpx_client().get)

reflex/utils/prerequisites.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ def check_latest_package_version(package_name: str):
115115
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
116116
return
117117
try:
118+
console.debug(f"Checking for the latest version of {package_name}...")
118119
# Get the latest version from PyPI
119120
current_version = importlib.metadata.version(package_name)
120121
url = f"https://pypi.org/pypi/{package_name}/json"
121122
response = net.get(url)
122123
latest_version = response.json()["info"]["version"]
124+
console.debug(f"Latest version of {package_name}: {latest_version}")
123125
if get_or_set_last_reflex_version_check_datetime():
124126
# Versions were already checked and saved in reflex.json, no need to warn again
125127
return
@@ -129,6 +131,7 @@ def check_latest_package_version(package_name: str):
129131
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
130132
)
131133
except Exception:
134+
console.debug(f"Failed to check for the latest version of {package_name}.")
132135
pass
133136

134137

@@ -902,11 +905,12 @@ def initialize_app_directory(
902905

903906
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
904907

905-
# Remove all pyc and __pycache__ dirs in template directory.
906-
for pyc_file in template_dir.glob("**/*.pyc"):
907-
pyc_file.unlink()
908-
for pycache_dir in template_dir.glob("**/__pycache__"):
909-
pycache_dir.rmdir()
908+
# Remove __pycache__ dirs in template directory and current directory.
909+
for pycache_dir in [
910+
*template_dir.glob("**/__pycache__"),
911+
*Path.cwd().glob("**/__pycache__"),
912+
]:
913+
shutil.rmtree(pycache_dir, ignore_errors=True)
910914

911915
for file in template_dir.iterdir():
912916
# Copy the file to current directory but keep the name the same.
@@ -950,16 +954,22 @@ def initialize_web_directory():
950954
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
951955
project_hash = get_project_hash()
952956

957+
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
953958
path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
954959

960+
console.debug("Initializing the web directory.")
955961
initialize_package_json()
956962

963+
console.debug("Initializing the bun config file.")
957964
initialize_bun_config()
958965

966+
console.debug("Initializing the public directory.")
959967
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
960968

969+
console.debug("Initializing the next.config.js file.")
961970
update_next_config()
962971

972+
console.debug("Initializing the reflex.json file.")
963973
# Initialize the reflex json file.
964974
init_reflex_json(project_hash=project_hash)
965975

@@ -1392,6 +1402,7 @@ def ensure_reflex_installation_id() -> int | None:
13921402
Distinct id.
13931403
"""
13941404
try:
1405+
console.debug("Ensuring reflex installation id.")
13951406
initialize_reflex_user_directory()
13961407
installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
13971408

@@ -1418,16 +1429,19 @@ def ensure_reflex_installation_id() -> int | None:
14181429

14191430
def initialize_reflex_user_directory():
14201431
"""Initialize the reflex user directory."""
1432+
console.debug(f"Creating {environment.REFLEX_DIR.get()}")
14211433
# Create the reflex directory.
14221434
path_ops.mkdir(environment.REFLEX_DIR.get())
14231435

14241436

14251437
def initialize_frontend_dependencies():
14261438
"""Initialize all the frontend dependencies."""
14271439
# validate dependencies before install
1440+
console.debug("Validating frontend dependencies.")
14281441
validate_frontend_dependencies()
14291442
# Install the frontend dependencies.
1430-
processes.run_concurrently(install_bun)
1443+
console.debug("Installing or validating bun.")
1444+
install_bun()
14311445
# Set up the web directory.
14321446
initialize_web_directory()
14331447

reflex/utils/redir.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import httpx
77

8+
from reflex.utils import net
9+
810
from .. import constants
911
from . import console
1012

@@ -38,7 +40,7 @@ def open_browser_and_wait(
3840
console.info("[b]Complete the workflow in the browser to continue.[/b]")
3941
while True:
4042
try:
41-
response = httpx.get(poll_url, follow_redirects=True)
43+
response = net.get(poll_url, follow_redirects=True)
4244
if response.is_success:
4345
break
4446
except httpx.RequestError as err:

reflex/utils/registry.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ def latency(registry: str) -> int:
1616
int: The latency of the registry in microseconds.
1717
"""
1818
try:
19-
return net.get(registry).elapsed.microseconds
19+
time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
2020
except httpx.HTTPError:
2121
console.info(f"Failed to connect to {registry}.")
2222
return 10_000_000
23+
else:
24+
console.debug(f"Latency of {registry}: {time_to_respond}")
25+
return time_to_respond
2326

2427

2528
def average_latency(registry: str, attempts: int = 3) -> int:
@@ -32,7 +35,9 @@ def average_latency(registry: str, attempts: int = 3) -> int:
3235
Returns:
3336
The average latency of the registry in microseconds.
3437
"""
35-
return sum(latency(registry) for _ in range(attempts)) // attempts
38+
registry_latency = sum(latency(registry) for _ in range(attempts)) // attempts
39+
console.debug(f"Average latency of {registry}: {registry_latency}")
40+
return registry_latency
3641

3742

3843
def _get_best_registry() -> str:
@@ -41,12 +46,15 @@ def _get_best_registry() -> str:
4146
Returns:
4247
The best registry.
4348
"""
49+
console.debug("Getting best registry...")
4450
registries = [
4551
"https://registry.npmjs.org",
4652
"https://r.cnpmjs.org",
4753
]
4854

49-
return min(registries, key=average_latency)
55+
best_registry = min(registries, key=average_latency)
56+
console.debug(f"Best registry: {best_registry}")
57+
return best_registry
5058

5159

5260
def get_npm_registry() -> str:

tests/units/test_state.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3436,6 +3436,30 @@ class GrandchildUsesMixinState(ChildMixinState):
34363436
pass
34373437

34383438

3439+
class BareMixin:
3440+
"""A bare mixin which does not inherit from rx.State."""
3441+
3442+
_bare_mixin: int = 0
3443+
3444+
3445+
class BareStateMixin(BareMixin, rx.State, mixin=True):
3446+
"""A state mixin that uses a bare mixin."""
3447+
3448+
pass
3449+
3450+
3451+
class BareMixinState(BareStateMixin, State):
3452+
"""A state that uses a bare mixin."""
3453+
3454+
pass
3455+
3456+
3457+
class ChildBareMixinState(BareMixinState):
3458+
"""A child state that uses a bare mixin."""
3459+
3460+
pass
3461+
3462+
34393463
def test_mixin_state() -> None:
34403464
"""Test that a mixin state works correctly."""
34413465
assert "num" in UsesMixinState.base_vars
@@ -3481,6 +3505,21 @@ def test_grandchild_mixin_state() -> None:
34813505
assert GrandchildUsesMixinState.get_root_state() == State
34823506

34833507

3508+
def test_bare_mixin_state() -> None:
3509+
"""Test that a mixin can inherit from a concrete state class."""
3510+
assert "_bare_mixin" not in BareMixinState.inherited_vars
3511+
assert "_bare_mixin" not in BareMixinState.base_vars
3512+
3513+
assert BareMixinState.get_parent_state() == State
3514+
assert BareMixinState.get_root_state() == State
3515+
3516+
assert "_bare_mixin" not in ChildBareMixinState.inherited_vars
3517+
assert "_bare_mixin" not in ChildBareMixinState.base_vars
3518+
3519+
assert ChildBareMixinState.get_parent_state() == BareMixinState
3520+
assert ChildBareMixinState.get_root_state() == State
3521+
3522+
34843523
def test_assignment_to_undeclared_vars():
34853524
"""Test that an attribute error is thrown when undeclared vars are set."""
34863525

0 commit comments

Comments
 (0)