reflex 0.7.4a1__py3-none-any.whl → 0.7.4a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +9 -1
- reflex/app.py +4 -4
- reflex/base.py +3 -3
- reflex/compiler/compiler.py +5 -0
- reflex/components/component.py +6 -3
- reflex/config.py +4 -1
- reflex/reflex.py +8 -4
- reflex/state.py +1 -1
- reflex/utils/exec.py +39 -33
- reflex/utils/net.py +107 -18
- reflex/utils/prerequisites.py +93 -8
- reflex/utils/processes.py +48 -16
- reflex/utils/redir.py +3 -1
- reflex/utils/registry.py +11 -3
- reflex/vars/base.py +0 -35
- reflex/vars/datetime.py +10 -34
- reflex/vars/number.py +16 -112
- reflex/vars/sequence.py +15 -108
- {reflex-0.7.4a1.dist-info → reflex-0.7.4a3.dist-info}/METADATA +3 -1
- {reflex-0.7.4a1.dist-info → reflex-0.7.4a3.dist-info}/RECORD +23 -23
- {reflex-0.7.4a1.dist-info → reflex-0.7.4a3.dist-info}/WHEEL +0 -0
- {reflex-0.7.4a1.dist-info → reflex-0.7.4a3.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.4a1.dist-info → reflex-0.7.4a3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useTheme } from "next-themes";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useRef, useEffect, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
ColorModeContext,
|
|
5
5
|
defaultColorMode,
|
|
@@ -13,6 +13,14 @@ export default function RadixThemesColorModeProvider({ children }) {
|
|
|
13
13
|
const [resolvedColorMode, setResolvedColorMode] = useState(
|
|
14
14
|
defaultColorMode === "dark" ? "dark" : "light",
|
|
15
15
|
);
|
|
16
|
+
const firstUpdate = useRef(true);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (firstUpdate.current) {
|
|
19
|
+
firstUpdate.current = false;
|
|
20
|
+
setRawColorMode(theme);
|
|
21
|
+
setResolvedColorMode(resolvedTheme);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
16
24
|
|
|
17
25
|
useEffect(() => {
|
|
18
26
|
if (isDevMode) {
|
reflex/app.py
CHANGED
|
@@ -968,7 +968,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
968
968
|
# Check the nocompile file.
|
|
969
969
|
if nocompile.exists():
|
|
970
970
|
# Delete the nocompile file
|
|
971
|
-
nocompile.unlink()
|
|
971
|
+
nocompile.unlink(missing_ok=True)
|
|
972
972
|
return False
|
|
973
973
|
|
|
974
974
|
# By default, compile the app.
|
|
@@ -1108,8 +1108,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1108
1108
|
if config.react_strict_mode:
|
|
1109
1109
|
app_wrappers[(200, "StrictMode")] = StrictMode.create()
|
|
1110
1110
|
|
|
1111
|
-
should_compile = self._should_compile()
|
|
1112
|
-
|
|
1113
1111
|
if not should_compile:
|
|
1114
1112
|
with console.timing("Evaluate Pages (Backend)"):
|
|
1115
1113
|
for route in self._unevaluated_pages:
|
|
@@ -1258,7 +1256,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1258
1256
|
compiler.compile_document_root(
|
|
1259
1257
|
self.head_components,
|
|
1260
1258
|
html_lang=self.html_lang,
|
|
1261
|
-
html_custom_attrs=
|
|
1259
|
+
html_custom_attrs=(
|
|
1260
|
+
{**self.html_custom_attrs} if self.html_custom_attrs else {}
|
|
1261
|
+
),
|
|
1262
1262
|
)
|
|
1263
1263
|
)
|
|
1264
1264
|
|
reflex/base.py
CHANGED
|
@@ -38,7 +38,7 @@ def validate_field_name(bases: list[Type["BaseModel"]], field_name: str) -> None
|
|
|
38
38
|
|
|
39
39
|
# monkeypatch pydantic validate_field_name method to skip validating
|
|
40
40
|
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
|
41
|
-
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [
|
|
41
|
+
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage]
|
|
42
42
|
|
|
43
43
|
if TYPE_CHECKING:
|
|
44
44
|
from reflex.vars import Var
|
|
@@ -107,7 +107,7 @@ class Base(BaseModel):
|
|
|
107
107
|
default_value: The default value of the field
|
|
108
108
|
"""
|
|
109
109
|
var_name = var._var_field_name
|
|
110
|
-
new_field = ModelField.infer(
|
|
110
|
+
new_field = ModelField.infer(
|
|
111
111
|
name=var_name,
|
|
112
112
|
value=default_value,
|
|
113
113
|
annotation=var._var_type,
|
|
@@ -128,5 +128,5 @@ class Base(BaseModel):
|
|
|
128
128
|
if isinstance(key, str):
|
|
129
129
|
# Seems like this function signature was wrong all along?
|
|
130
130
|
# If the user wants a field that we know of, get it and pass it off to _get_value
|
|
131
|
-
return getattr(self, key
|
|
131
|
+
return getattr(self, key)
|
|
132
132
|
return key
|
reflex/compiler/compiler.py
CHANGED
|
@@ -20,6 +20,7 @@ from reflex.config import environment, get_config
|
|
|
20
20
|
from reflex.state import BaseState
|
|
21
21
|
from reflex.style import SYSTEM_COLOR_MODE
|
|
22
22
|
from reflex.utils import console, path_ops
|
|
23
|
+
from reflex.utils.exceptions import ReflexError
|
|
23
24
|
from reflex.utils.exec import is_prod_mode
|
|
24
25
|
from reflex.utils.imports import ImportVar
|
|
25
26
|
from reflex.utils.prerequisites import get_web_dir
|
|
@@ -651,6 +652,8 @@ def into_component(component: Component | ComponentCallable) -> Component:
|
|
|
651
652
|
):
|
|
652
653
|
return converted
|
|
653
654
|
except KeyError as e:
|
|
655
|
+
if isinstance(e, ReflexError):
|
|
656
|
+
raise
|
|
654
657
|
key = e.args[0] if e.args else None
|
|
655
658
|
if key is not None and isinstance(key, Var):
|
|
656
659
|
raise TypeError(
|
|
@@ -658,6 +661,8 @@ def into_component(component: Component | ComponentCallable) -> Component:
|
|
|
658
661
|
).with_traceback(e.__traceback__) from None
|
|
659
662
|
raise
|
|
660
663
|
except TypeError as e:
|
|
664
|
+
if isinstance(e, ReflexError):
|
|
665
|
+
raise
|
|
661
666
|
message = e.args[0] if e.args else None
|
|
662
667
|
if message and isinstance(message, str):
|
|
663
668
|
if message.endswith("has no len()") and (
|
reflex/components/component.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import copy
|
|
6
7
|
import dataclasses
|
|
7
8
|
import functools
|
|
@@ -1974,13 +1975,15 @@ class NoSSRComponent(Component):
|
|
|
1974
1975
|
# Do NOT import the main library/tag statically.
|
|
1975
1976
|
import_name = self._get_import_name()
|
|
1976
1977
|
if import_name is not None:
|
|
1977
|
-
|
|
1978
|
+
with contextlib.suppress(ValueError):
|
|
1979
|
+
_imports[import_name].remove(self.import_var)
|
|
1980
|
+
_imports[import_name].append(
|
|
1978
1981
|
imports.ImportVar(
|
|
1979
1982
|
tag=None,
|
|
1980
1983
|
render=False,
|
|
1981
1984
|
transpile=self._should_transpile(self.library),
|
|
1982
|
-
)
|
|
1983
|
-
|
|
1985
|
+
)
|
|
1986
|
+
)
|
|
1984
1987
|
|
|
1985
1988
|
return imports.merge_imports(
|
|
1986
1989
|
dynamic_import,
|
reflex/config.py
CHANGED
|
@@ -44,7 +44,7 @@ from reflex.utils.types import (
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
try:
|
|
47
|
-
from dotenv import load_dotenv
|
|
47
|
+
from dotenv import load_dotenv
|
|
48
48
|
except ImportError:
|
|
49
49
|
load_dotenv = None
|
|
50
50
|
|
|
@@ -720,6 +720,9 @@ class EnvironmentVariables:
|
|
|
720
720
|
# Used by flexgen to enumerate the pages.
|
|
721
721
|
REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
|
|
722
722
|
|
|
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
|
+
|
|
723
726
|
|
|
724
727
|
environment = EnvironmentVariables()
|
|
725
728
|
|
reflex/reflex.py
CHANGED
|
@@ -205,18 +205,22 @@ def _run(
|
|
|
205
205
|
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
|
|
206
206
|
|
|
207
207
|
# Get the app module.
|
|
208
|
-
app_task = prerequisites.
|
|
208
|
+
app_task = prerequisites.compile_or_validate_app
|
|
209
|
+
args = (frontend,)
|
|
209
210
|
|
|
210
211
|
# Granian fails if the app is already imported.
|
|
211
212
|
if should_use_granian():
|
|
212
213
|
import concurrent.futures
|
|
213
214
|
|
|
214
215
|
compile_future = concurrent.futures.ProcessPoolExecutor(max_workers=1).submit(
|
|
215
|
-
app_task
|
|
216
|
+
app_task,
|
|
217
|
+
*args,
|
|
216
218
|
)
|
|
217
|
-
compile_future.result()
|
|
219
|
+
validation_result = compile_future.result()
|
|
218
220
|
else:
|
|
219
|
-
app_task()
|
|
221
|
+
validation_result = app_task(*args)
|
|
222
|
+
if not validation_result:
|
|
223
|
+
raise typer.Exit(1)
|
|
220
224
|
|
|
221
225
|
# Warn if schema is not up to date.
|
|
222
226
|
prerequisites.check_schema_up_to_date()
|
reflex/state.py
CHANGED
|
@@ -907,7 +907,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
907
907
|
raise ValueError(f"Only one parent state is allowed {parent_states}.")
|
|
908
908
|
# The first non-mixin state in the mro is our parent.
|
|
909
909
|
for base in cls.mro()[1:]:
|
|
910
|
-
if
|
|
910
|
+
if not issubclass(base, BaseState) or base._mixin:
|
|
911
911
|
continue
|
|
912
912
|
if base is BaseState:
|
|
913
913
|
break
|
reflex/utils/exec.py
CHANGED
|
@@ -189,9 +189,11 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
|
|
|
189
189
|
|
|
190
190
|
@once
|
|
191
191
|
def _warn_user_about_uvicorn():
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
# When we eventually switch to Granian by default, we should enable this warning.
|
|
193
|
+
if False:
|
|
194
|
+
console.warn(
|
|
195
|
+
"Using Uvicorn for backend as it is installed. This behavior will change in 0.8.0 to use Granian by default."
|
|
196
|
+
)
|
|
195
197
|
|
|
196
198
|
|
|
197
199
|
def should_use_granian():
|
|
@@ -357,70 +359,74 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
357
359
|
).serve()
|
|
358
360
|
|
|
359
361
|
|
|
362
|
+
def _deprecate_asgi_config(
|
|
363
|
+
config_name: str,
|
|
364
|
+
reason: str = "",
|
|
365
|
+
):
|
|
366
|
+
# When we eventually switch to Granian by default, we should enable this deprecation.
|
|
367
|
+
if False:
|
|
368
|
+
console.deprecate(
|
|
369
|
+
f"config.{config_name}",
|
|
370
|
+
reason=reason,
|
|
371
|
+
deprecation_version="0.7.5",
|
|
372
|
+
removal_version="0.8.0",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
360
376
|
@once
|
|
361
377
|
def _get_backend_workers():
|
|
362
378
|
from reflex.utils import processes
|
|
363
379
|
|
|
364
380
|
config = get_config()
|
|
365
381
|
|
|
382
|
+
gunicorn_workers = config.gunicorn_workers or 0
|
|
383
|
+
|
|
366
384
|
if config.gunicorn_workers is not None:
|
|
367
|
-
|
|
368
|
-
"
|
|
369
|
-
|
|
370
|
-
deprecation_version="0.7.4",
|
|
371
|
-
removal_version="0.8.0",
|
|
385
|
+
_deprecate_asgi_config(
|
|
386
|
+
"gunicorn_workers",
|
|
387
|
+
"If you're using Granian, use GRANIAN_WORKERS instead.",
|
|
372
388
|
)
|
|
373
389
|
|
|
374
|
-
return (
|
|
375
|
-
processes.get_num_workers()
|
|
376
|
-
if not config.gunicorn_workers
|
|
377
|
-
else config.gunicorn_workers
|
|
378
|
-
)
|
|
390
|
+
return gunicorn_workers if gunicorn_workers else processes.get_num_workers()
|
|
379
391
|
|
|
380
392
|
|
|
381
393
|
@once
|
|
382
394
|
def _get_backend_timeout():
|
|
383
395
|
config = get_config()
|
|
384
396
|
|
|
397
|
+
timeout = config.timeout or 120
|
|
398
|
+
|
|
385
399
|
if config.timeout is not None:
|
|
386
|
-
|
|
387
|
-
"
|
|
388
|
-
|
|
389
|
-
deprecation_version="0.7.4",
|
|
390
|
-
removal_version="0.8.0",
|
|
400
|
+
_deprecate_asgi_config(
|
|
401
|
+
"timeout",
|
|
402
|
+
"If you're using Granian, use GRANIAN_WORKERS_LIFETIME instead.",
|
|
391
403
|
)
|
|
392
404
|
|
|
393
|
-
return
|
|
405
|
+
return timeout
|
|
394
406
|
|
|
395
407
|
|
|
396
408
|
@once
|
|
397
409
|
def _get_backend_max_requests():
|
|
398
410
|
config = get_config()
|
|
399
411
|
|
|
412
|
+
gunicorn_max_requests = config.gunicorn_max_requests or 120
|
|
413
|
+
|
|
400
414
|
if config.gunicorn_max_requests is not None:
|
|
401
|
-
|
|
402
|
-
"config.gunicorn_max_requests",
|
|
403
|
-
reason="",
|
|
404
|
-
deprecation_version="0.7.4",
|
|
405
|
-
removal_version="0.8.0",
|
|
406
|
-
)
|
|
415
|
+
_deprecate_asgi_config("gunicorn_max_requests")
|
|
407
416
|
|
|
408
|
-
return
|
|
417
|
+
return gunicorn_max_requests
|
|
409
418
|
|
|
410
419
|
|
|
411
420
|
@once
|
|
412
421
|
def _get_backend_max_requests_jitter():
|
|
413
422
|
config = get_config()
|
|
414
423
|
|
|
424
|
+
gunicorn_max_requests_jitter = config.gunicorn_max_requests_jitter or 25
|
|
425
|
+
|
|
415
426
|
if config.gunicorn_max_requests_jitter is not None:
|
|
416
|
-
|
|
417
|
-
"config.gunicorn_max_requests_jitter",
|
|
418
|
-
reason="",
|
|
419
|
-
deprecation_version="0.7.4",
|
|
420
|
-
removal_version="0.8.0",
|
|
421
|
-
)
|
|
427
|
+
_deprecate_asgi_config("gunicorn_max_requests_jitter")
|
|
422
428
|
|
|
423
|
-
return
|
|
429
|
+
return gunicorn_max_requests_jitter
|
|
424
430
|
|
|
425
431
|
|
|
426
432
|
def run_backend_prod(
|
reflex/utils/net.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"""Helpers for downloading files from the network."""
|
|
2
2
|
|
|
3
|
+
import functools
|
|
4
|
+
import time
|
|
5
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
6
|
+
|
|
3
7
|
import httpx
|
|
4
8
|
|
|
5
|
-
from
|
|
9
|
+
from reflex.utils.decorator import once
|
|
10
|
+
|
|
6
11
|
from . import console
|
|
7
12
|
|
|
8
13
|
|
|
@@ -12,30 +17,114 @@ def _httpx_verify_kwarg() -> bool:
|
|
|
12
17
|
Returns:
|
|
13
18
|
True if SSL verification is enabled, False otherwise
|
|
14
19
|
"""
|
|
20
|
+
from ..config import environment
|
|
21
|
+
|
|
15
22
|
return not environment.SSL_NO_VERIFY.get()
|
|
16
23
|
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
20
33
|
|
|
21
34
|
Args:
|
|
22
|
-
|
|
23
|
-
**kwargs: Additional keyword arguments to pass to httpx.get.
|
|
35
|
+
func: The function to wrap.
|
|
24
36
|
|
|
25
37
|
Returns:
|
|
26
|
-
The
|
|
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
|
+
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
30
70
|
"""
|
|
31
|
-
kwargs.setdefault("verify", _httpx_verify_kwarg())
|
|
32
71
|
try:
|
|
33
|
-
|
|
34
|
-
except httpx.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
CHANGED
|
@@ -8,6 +8,7 @@ import functools
|
|
|
8
8
|
import importlib
|
|
9
9
|
import importlib.metadata
|
|
10
10
|
import importlib.util
|
|
11
|
+
import io
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import platform
|
|
@@ -114,11 +115,13 @@ def check_latest_package_version(package_name: str):
|
|
|
114
115
|
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
|
|
115
116
|
return
|
|
116
117
|
try:
|
|
118
|
+
console.debug(f"Checking for the latest version of {package_name}...")
|
|
117
119
|
# Get the latest version from PyPI
|
|
118
120
|
current_version = importlib.metadata.version(package_name)
|
|
119
121
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
120
122
|
response = net.get(url)
|
|
121
123
|
latest_version = response.json()["info"]["version"]
|
|
124
|
+
console.debug(f"Latest version of {package_name}: {latest_version}")
|
|
122
125
|
if get_or_set_last_reflex_version_check_datetime():
|
|
123
126
|
# Versions were already checked and saved in reflex.json, no need to warn again
|
|
124
127
|
return
|
|
@@ -128,6 +131,7 @@ def check_latest_package_version(package_name: str):
|
|
|
128
131
|
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
|
|
129
132
|
)
|
|
130
133
|
except Exception:
|
|
134
|
+
console.debug(f"Failed to check for the latest version of {package_name}.")
|
|
131
135
|
pass
|
|
132
136
|
|
|
133
137
|
|
|
@@ -183,7 +187,7 @@ def get_node_version() -> version.Version | None:
|
|
|
183
187
|
try:
|
|
184
188
|
result = processes.new_process([node_path, "-v"], run=True)
|
|
185
189
|
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
186
|
-
return version.parse(result.stdout)
|
|
190
|
+
return version.parse(result.stdout)
|
|
187
191
|
except (FileNotFoundError, TypeError):
|
|
188
192
|
return None
|
|
189
193
|
|
|
@@ -200,7 +204,7 @@ def get_bun_version() -> version.Version | None:
|
|
|
200
204
|
try:
|
|
201
205
|
# Run the bun -v command and capture the output
|
|
202
206
|
result = processes.new_process([str(bun_path), "-v"], run=True)
|
|
203
|
-
return version.parse(str(result.stdout))
|
|
207
|
+
return version.parse(str(result.stdout))
|
|
204
208
|
except FileNotFoundError:
|
|
205
209
|
return None
|
|
206
210
|
except version.InvalidVersion as e:
|
|
@@ -449,6 +453,76 @@ def compile_app(reload: bool = False, export: bool = False) -> None:
|
|
|
449
453
|
get_compiled_app(reload=reload, export=export)
|
|
450
454
|
|
|
451
455
|
|
|
456
|
+
def _can_colorize() -> bool:
|
|
457
|
+
"""Check if the output can be colorized.
|
|
458
|
+
|
|
459
|
+
Copied from _colorize.can_colorize.
|
|
460
|
+
|
|
461
|
+
https://raw.githubusercontent.com/python/cpython/refs/heads/main/Lib/_colorize.py
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
If the output can be colorized
|
|
465
|
+
"""
|
|
466
|
+
file = sys.stdout
|
|
467
|
+
|
|
468
|
+
if not sys.flags.ignore_environment:
|
|
469
|
+
if os.environ.get("PYTHON_COLORS") == "0":
|
|
470
|
+
return False
|
|
471
|
+
if os.environ.get("PYTHON_COLORS") == "1":
|
|
472
|
+
return True
|
|
473
|
+
if os.environ.get("NO_COLOR"):
|
|
474
|
+
return False
|
|
475
|
+
if os.environ.get("FORCE_COLOR"):
|
|
476
|
+
return True
|
|
477
|
+
if os.environ.get("TERM") == "dumb":
|
|
478
|
+
return False
|
|
479
|
+
|
|
480
|
+
if not hasattr(file, "fileno"):
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
if sys.platform == "win32":
|
|
484
|
+
try:
|
|
485
|
+
import nt
|
|
486
|
+
|
|
487
|
+
if not nt._supports_virtual_terminal():
|
|
488
|
+
return False
|
|
489
|
+
except (ImportError, AttributeError):
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
try:
|
|
493
|
+
return os.isatty(file.fileno())
|
|
494
|
+
except io.UnsupportedOperation:
|
|
495
|
+
return file.isatty()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def compile_or_validate_app(compile: bool = False) -> bool:
|
|
499
|
+
"""Compile or validate the app module based on the default config.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
compile: Whether to compile the app.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
If the app is compiled successfully.
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
if compile:
|
|
509
|
+
compile_app()
|
|
510
|
+
else:
|
|
511
|
+
validate_app()
|
|
512
|
+
except Exception as e:
|
|
513
|
+
import traceback
|
|
514
|
+
|
|
515
|
+
sys_exception = sys.exception()
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
colorize = _can_colorize()
|
|
519
|
+
traceback.print_exception(e, colorize=colorize) # pyright: ignore[reportCallIssue]
|
|
520
|
+
except Exception:
|
|
521
|
+
traceback.print_exception(sys_exception)
|
|
522
|
+
return False
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
|
|
452
526
|
def get_redis() -> Redis | None:
|
|
453
527
|
"""Get the asynchronous redis client.
|
|
454
528
|
|
|
@@ -831,11 +905,12 @@ def initialize_app_directory(
|
|
|
831
905
|
|
|
832
906
|
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
|
|
833
907
|
|
|
834
|
-
# Remove
|
|
835
|
-
for
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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)
|
|
839
914
|
|
|
840
915
|
for file in template_dir.iterdir():
|
|
841
916
|
# Copy the file to current directory but keep the name the same.
|
|
@@ -879,16 +954,22 @@ def initialize_web_directory():
|
|
|
879
954
|
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
|
|
880
955
|
project_hash = get_project_hash()
|
|
881
956
|
|
|
957
|
+
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
|
|
882
958
|
path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
|
|
883
959
|
|
|
960
|
+
console.debug("Initializing the web directory.")
|
|
884
961
|
initialize_package_json()
|
|
885
962
|
|
|
963
|
+
console.debug("Initializing the bun config file.")
|
|
886
964
|
initialize_bun_config()
|
|
887
965
|
|
|
966
|
+
console.debug("Initializing the public directory.")
|
|
888
967
|
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
889
968
|
|
|
969
|
+
console.debug("Initializing the next.config.js file.")
|
|
890
970
|
update_next_config()
|
|
891
971
|
|
|
972
|
+
console.debug("Initializing the reflex.json file.")
|
|
892
973
|
# Initialize the reflex json file.
|
|
893
974
|
init_reflex_json(project_hash=project_hash)
|
|
894
975
|
|
|
@@ -1321,6 +1402,7 @@ def ensure_reflex_installation_id() -> int | None:
|
|
|
1321
1402
|
Distinct id.
|
|
1322
1403
|
"""
|
|
1323
1404
|
try:
|
|
1405
|
+
console.debug("Ensuring reflex installation id.")
|
|
1324
1406
|
initialize_reflex_user_directory()
|
|
1325
1407
|
installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
|
|
1326
1408
|
|
|
@@ -1347,6 +1429,7 @@ def ensure_reflex_installation_id() -> int | None:
|
|
|
1347
1429
|
|
|
1348
1430
|
def initialize_reflex_user_directory():
|
|
1349
1431
|
"""Initialize the reflex user directory."""
|
|
1432
|
+
console.debug(f"Creating {environment.REFLEX_DIR.get()}")
|
|
1350
1433
|
# Create the reflex directory.
|
|
1351
1434
|
path_ops.mkdir(environment.REFLEX_DIR.get())
|
|
1352
1435
|
|
|
@@ -1354,9 +1437,11 @@ def initialize_reflex_user_directory():
|
|
|
1354
1437
|
def initialize_frontend_dependencies():
|
|
1355
1438
|
"""Initialize all the frontend dependencies."""
|
|
1356
1439
|
# validate dependencies before install
|
|
1440
|
+
console.debug("Validating frontend dependencies.")
|
|
1357
1441
|
validate_frontend_dependencies()
|
|
1358
1442
|
# Install the frontend dependencies.
|
|
1359
|
-
|
|
1443
|
+
console.debug("Installing or validating bun.")
|
|
1444
|
+
install_bun()
|
|
1360
1445
|
# Set up the web directory.
|
|
1361
1446
|
initialize_web_directory()
|
|
1362
1447
|
|