reflex 0.6.0a3__py3-none-any.whl → 0.6.1__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/jinja/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +14 -0
- reflex/.templates/web/utils/state.js +70 -41
- reflex/app.py +11 -11
- reflex/app_mixins/lifespan.py +24 -6
- reflex/app_module_for_backend.py +1 -1
- reflex/base.py +7 -13
- reflex/compiler/utils.py +17 -8
- reflex/components/base/bare.py +3 -1
- reflex/components/base/meta.py +5 -3
- reflex/components/component.py +28 -20
- reflex/components/core/breakpoints.py +1 -3
- reflex/components/core/cond.py +4 -4
- reflex/components/datadisplay/__init__.py +0 -1
- reflex/components/datadisplay/__init__.pyi +0 -1
- reflex/components/datadisplay/code.py +93 -106
- reflex/components/datadisplay/code.pyi +710 -53
- reflex/components/datadisplay/logo.py +22 -20
- reflex/components/dynamic.py +157 -0
- reflex/components/el/elements/forms.py +4 -1
- reflex/components/gridjs/datatable.py +2 -1
- reflex/components/markdown/markdown.py +10 -6
- reflex/components/markdown/markdown.pyi +3 -0
- reflex/components/radix/themes/components/progress.py +22 -0
- reflex/components/radix/themes/components/progress.pyi +2 -0
- reflex/components/radix/themes/components/segmented_control.py +3 -0
- reflex/components/radix/themes/components/segmented_control.pyi +2 -0
- reflex/components/radix/themes/layout/stack.py +1 -1
- reflex/components/recharts/cartesian.py +1 -1
- reflex/components/sonner/toast.py +3 -3
- reflex/components/tags/iter_tag.py +5 -1
- reflex/config.py +2 -2
- reflex/constants/base.py +4 -1
- reflex/constants/installer.py +8 -1
- reflex/event.py +63 -22
- reflex/experimental/assets.py +3 -1
- reflex/experimental/client_state.py +12 -7
- reflex/experimental/misc.py +5 -3
- reflex/middleware/hydrate_middleware.py +1 -2
- reflex/page.py +10 -3
- reflex/reflex.py +20 -3
- reflex/state.py +105 -44
- reflex/style.py +12 -2
- reflex/testing.py +8 -4
- reflex/utils/console.py +1 -1
- reflex/utils/exceptions.py +4 -0
- reflex/utils/exec.py +170 -18
- reflex/utils/format.py +6 -44
- reflex/utils/path_ops.py +36 -1
- reflex/utils/prerequisites.py +62 -21
- reflex/utils/serializers.py +7 -46
- reflex/utils/telemetry.py +1 -1
- reflex/utils/types.py +18 -3
- reflex/vars/base.py +303 -43
- reflex/vars/number.py +3 -0
- reflex/vars/sequence.py +43 -8
- {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/METADATA +5 -5
- {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/RECORD +61 -60
- {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/LICENSE +0 -0
- {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/WHEEL +0 -0
- {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/entry_points.txt +0 -0
reflex/testing.py
CHANGED
|
@@ -340,6 +340,9 @@ class AppHarness:
|
|
|
340
340
|
|
|
341
341
|
This is necessary when the backend is restarted and the state manager is a
|
|
342
342
|
StateManagerRedis instance.
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
RuntimeError: when the state manager cannot be reset
|
|
343
346
|
"""
|
|
344
347
|
if (
|
|
345
348
|
self.app_instance is not None
|
|
@@ -354,7 +357,8 @@ class AppHarness:
|
|
|
354
357
|
self.app_instance._state_manager = StateManagerRedis.create(
|
|
355
358
|
state=self.app_instance.state,
|
|
356
359
|
)
|
|
357
|
-
|
|
360
|
+
if not isinstance(self.app_instance.state_manager, StateManagerRedis):
|
|
361
|
+
raise RuntimeError("Failed to reset state manager.")
|
|
358
362
|
|
|
359
363
|
def _start_frontend(self):
|
|
360
364
|
# Set up the frontend.
|
|
@@ -787,13 +791,13 @@ class AppHarness:
|
|
|
787
791
|
Raises:
|
|
788
792
|
RuntimeError: when the app hasn't started running
|
|
789
793
|
TimeoutError: when the timeout expires before any states are seen
|
|
794
|
+
ValueError: when the state_manager is not a memory state manager
|
|
790
795
|
"""
|
|
791
796
|
if self.app_instance is None:
|
|
792
797
|
raise RuntimeError("App is not running.")
|
|
793
798
|
state_manager = self.app_instance.state_manager
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
), "Only works with memory state manager"
|
|
799
|
+
if not isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
|
|
800
|
+
raise ValueError("Only works with memory or disk state manager")
|
|
797
801
|
if not self._poll_for(
|
|
798
802
|
target=lambda: state_manager.states,
|
|
799
803
|
timeout=timeout,
|
reflex/utils/console.py
CHANGED
|
@@ -58,7 +58,7 @@ def debug(msg: str, **kwargs):
|
|
|
58
58
|
kwargs: Keyword arguments to pass to the print function.
|
|
59
59
|
"""
|
|
60
60
|
if is_debug():
|
|
61
|
-
msg_ = f"[
|
|
61
|
+
msg_ = f"[purple]Debug: {msg}[/purple]"
|
|
62
62
|
if progress := kwargs.pop("progress", None):
|
|
63
63
|
progress.console.print(msg_, **kwargs)
|
|
64
64
|
else:
|
reflex/utils/exceptions.py
CHANGED
|
@@ -111,3 +111,7 @@ class GeneratedCodeHasNoFunctionDefs(ReflexError):
|
|
|
111
111
|
|
|
112
112
|
class PrimitiveUnserializableToJSON(ReflexError, ValueError):
|
|
113
113
|
"""Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class InvalidLifespanTaskType(ReflexError, TypeError):
|
|
117
|
+
"""Raised when an invalid task type is registered as a lifespan task."""
|
reflex/utils/exec.py
CHANGED
|
@@ -16,6 +16,7 @@ import psutil
|
|
|
16
16
|
|
|
17
17
|
from reflex import constants
|
|
18
18
|
from reflex.config import get_config
|
|
19
|
+
from reflex.constants.base import LogLevel
|
|
19
20
|
from reflex.utils import console, path_ops
|
|
20
21
|
from reflex.utils.prerequisites import get_web_dir
|
|
21
22
|
|
|
@@ -60,6 +61,13 @@ def kill(proc_pid: int):
|
|
|
60
61
|
process.kill()
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
def notify_backend():
|
|
65
|
+
"""Output a string notifying where the backend is running."""
|
|
66
|
+
console.print(
|
|
67
|
+
f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
63
71
|
# run_process_and_launch_url is assumed to be used
|
|
64
72
|
# only to launch the frontend
|
|
65
73
|
# If this is not the case, might have to change the logic
|
|
@@ -103,9 +111,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
|
|
|
103
111
|
f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
|
|
104
112
|
)
|
|
105
113
|
if backend_present:
|
|
106
|
-
|
|
107
|
-
f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
|
|
108
|
-
)
|
|
114
|
+
notify_backend()
|
|
109
115
|
first_run = False
|
|
110
116
|
else:
|
|
111
117
|
console.print("New packages detected: Updating app...")
|
|
@@ -172,10 +178,42 @@ def run_frontend_prod(root: Path, port: str, backend_present=True):
|
|
|
172
178
|
)
|
|
173
179
|
|
|
174
180
|
|
|
181
|
+
def should_use_granian():
|
|
182
|
+
"""Whether to use Granian for backend.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if Granian should be used.
|
|
186
|
+
"""
|
|
187
|
+
return os.getenv("REFLEX_USE_GRANIAN", "0") == "1"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_app_module():
|
|
191
|
+
"""Get the app module for the backend.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The app module for the backend.
|
|
195
|
+
"""
|
|
196
|
+
return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_granian_target():
|
|
200
|
+
"""Get the Granian target for the backend.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The Granian target for the backend.
|
|
204
|
+
"""
|
|
205
|
+
import reflex
|
|
206
|
+
|
|
207
|
+
app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
|
|
208
|
+
|
|
209
|
+
return f"{str(app_module_path)}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
|
|
210
|
+
|
|
211
|
+
|
|
175
212
|
def run_backend(
|
|
176
213
|
host: str,
|
|
177
214
|
port: int,
|
|
178
215
|
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
216
|
+
frontend_present: bool = False,
|
|
179
217
|
):
|
|
180
218
|
"""Run the backend.
|
|
181
219
|
|
|
@@ -183,25 +221,82 @@ def run_backend(
|
|
|
183
221
|
host: The app host
|
|
184
222
|
port: The app port
|
|
185
223
|
loglevel: The log level.
|
|
224
|
+
frontend_present: Whether the frontend is present.
|
|
186
225
|
"""
|
|
187
|
-
import uvicorn
|
|
188
|
-
|
|
189
|
-
config = get_config()
|
|
190
|
-
app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
|
|
191
|
-
|
|
192
226
|
web_dir = get_web_dir()
|
|
193
227
|
# Create a .nocompile file to skip compile for backend.
|
|
194
228
|
if web_dir.exists():
|
|
195
229
|
(web_dir / constants.NOCOMPILE_FILE).touch()
|
|
196
230
|
|
|
231
|
+
if not frontend_present:
|
|
232
|
+
notify_backend()
|
|
233
|
+
|
|
197
234
|
# Run the backend in development mode.
|
|
235
|
+
if should_use_granian():
|
|
236
|
+
run_granian_backend(host, port, loglevel)
|
|
237
|
+
else:
|
|
238
|
+
run_uvicorn_backend(host, port, loglevel)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def run_uvicorn_backend(host, port, loglevel: LogLevel):
|
|
242
|
+
"""Run the backend in development mode using Uvicorn.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
host: The app host
|
|
246
|
+
port: The app port
|
|
247
|
+
loglevel: The log level.
|
|
248
|
+
"""
|
|
249
|
+
import uvicorn
|
|
250
|
+
|
|
198
251
|
uvicorn.run(
|
|
199
|
-
app=f"{
|
|
252
|
+
app=f"{get_app_module()}.{constants.CompileVars.API}",
|
|
200
253
|
host=host,
|
|
201
254
|
port=port,
|
|
202
255
|
log_level=loglevel.value,
|
|
203
256
|
reload=True,
|
|
204
|
-
reload_dirs=[
|
|
257
|
+
reload_dirs=[get_config().app_name],
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def run_granian_backend(host, port, loglevel: LogLevel):
|
|
262
|
+
"""Run the backend in development mode using Granian.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
host: The app host
|
|
266
|
+
port: The app port
|
|
267
|
+
loglevel: The log level.
|
|
268
|
+
"""
|
|
269
|
+
console.debug("Using Granian for backend")
|
|
270
|
+
try:
|
|
271
|
+
from granian import Granian # type: ignore
|
|
272
|
+
from granian.constants import Interfaces # type: ignore
|
|
273
|
+
from granian.log import LogLevels # type: ignore
|
|
274
|
+
|
|
275
|
+
Granian(
|
|
276
|
+
target=get_granian_target(),
|
|
277
|
+
address=host,
|
|
278
|
+
port=port,
|
|
279
|
+
interface=Interfaces.ASGI,
|
|
280
|
+
log_level=LogLevels(loglevel.value),
|
|
281
|
+
reload=True,
|
|
282
|
+
reload_paths=[Path(get_config().app_name)],
|
|
283
|
+
reload_ignore_dirs=[".web"],
|
|
284
|
+
).serve()
|
|
285
|
+
except ImportError:
|
|
286
|
+
console.error(
|
|
287
|
+
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
|
|
288
|
+
)
|
|
289
|
+
os._exit(1)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _get_backend_workers():
|
|
293
|
+
from reflex.utils import processes
|
|
294
|
+
|
|
295
|
+
config = get_config()
|
|
296
|
+
return (
|
|
297
|
+
processes.get_num_workers()
|
|
298
|
+
if not config.gunicorn_workers
|
|
299
|
+
else config.gunicorn_workers
|
|
205
300
|
)
|
|
206
301
|
|
|
207
302
|
|
|
@@ -209,9 +304,28 @@ def run_backend_prod(
|
|
|
209
304
|
host: str,
|
|
210
305
|
port: int,
|
|
211
306
|
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
|
307
|
+
frontend_present: bool = False,
|
|
212
308
|
):
|
|
213
309
|
"""Run the backend.
|
|
214
310
|
|
|
311
|
+
Args:
|
|
312
|
+
host: The app host
|
|
313
|
+
port: The app port
|
|
314
|
+
loglevel: The log level.
|
|
315
|
+
frontend_present: Whether the frontend is present.
|
|
316
|
+
"""
|
|
317
|
+
if not frontend_present:
|
|
318
|
+
notify_backend()
|
|
319
|
+
|
|
320
|
+
if should_use_granian():
|
|
321
|
+
run_granian_backend_prod(host, port, loglevel)
|
|
322
|
+
else:
|
|
323
|
+
run_uvicorn_backend_prod(host, port, loglevel)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def run_uvicorn_backend_prod(host, port, loglevel):
|
|
327
|
+
"""Run the backend in production mode using Uvicorn.
|
|
328
|
+
|
|
215
329
|
Args:
|
|
216
330
|
host: The app host
|
|
217
331
|
port: The app port
|
|
@@ -220,14 +334,11 @@ def run_backend_prod(
|
|
|
220
334
|
from reflex.utils import processes
|
|
221
335
|
|
|
222
336
|
config = get_config()
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else config.gunicorn_workers
|
|
227
|
-
)
|
|
337
|
+
|
|
338
|
+
app_module = get_app_module()
|
|
339
|
+
|
|
228
340
|
RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
|
|
229
341
|
RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
|
|
230
|
-
app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
|
|
231
342
|
command = (
|
|
232
343
|
[
|
|
233
344
|
*RUN_BACKEND_PROD_WINDOWS,
|
|
@@ -243,7 +354,7 @@ def run_backend_prod(
|
|
|
243
354
|
"--bind",
|
|
244
355
|
f"{host}:{port}",
|
|
245
356
|
"--threads",
|
|
246
|
-
str(
|
|
357
|
+
str(_get_backend_workers()),
|
|
247
358
|
f"{app_module}()",
|
|
248
359
|
]
|
|
249
360
|
)
|
|
@@ -252,7 +363,7 @@ def run_backend_prod(
|
|
|
252
363
|
"--log-level",
|
|
253
364
|
loglevel.value,
|
|
254
365
|
"--workers",
|
|
255
|
-
str(
|
|
366
|
+
str(_get_backend_workers()),
|
|
256
367
|
]
|
|
257
368
|
processes.new_process(
|
|
258
369
|
command,
|
|
@@ -262,6 +373,47 @@ def run_backend_prod(
|
|
|
262
373
|
)
|
|
263
374
|
|
|
264
375
|
|
|
376
|
+
def run_granian_backend_prod(host, port, loglevel):
|
|
377
|
+
"""Run the backend in production mode using Granian.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
host: The app host
|
|
381
|
+
port: The app port
|
|
382
|
+
loglevel: The log level.
|
|
383
|
+
"""
|
|
384
|
+
from reflex.utils import processes
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
from granian.constants import Interfaces # type: ignore
|
|
388
|
+
|
|
389
|
+
command = [
|
|
390
|
+
"granian",
|
|
391
|
+
"--workers",
|
|
392
|
+
str(_get_backend_workers()),
|
|
393
|
+
"--log-level",
|
|
394
|
+
"critical",
|
|
395
|
+
"--host",
|
|
396
|
+
host,
|
|
397
|
+
"--port",
|
|
398
|
+
str(port),
|
|
399
|
+
"--interface",
|
|
400
|
+
str(Interfaces.ASGI),
|
|
401
|
+
get_granian_target(),
|
|
402
|
+
]
|
|
403
|
+
processes.new_process(
|
|
404
|
+
command,
|
|
405
|
+
run=True,
|
|
406
|
+
show_logs=True,
|
|
407
|
+
env={
|
|
408
|
+
constants.SKIP_COMPILE_ENV_VAR: "yes"
|
|
409
|
+
}, # skip compile for prod backend
|
|
410
|
+
)
|
|
411
|
+
except ImportError:
|
|
412
|
+
console.error(
|
|
413
|
+
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
265
417
|
def output_system_info():
|
|
266
418
|
"""Show system information if the loglevel is in DEBUG."""
|
|
267
419
|
if console._LOG_LEVEL > constants.LogLevel.DEBUG:
|
reflex/utils/format.py
CHANGED
|
@@ -9,7 +9,7 @@ import re
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
10
10
|
|
|
11
11
|
from reflex import constants
|
|
12
|
-
from reflex.utils import exceptions
|
|
12
|
+
from reflex.utils import exceptions
|
|
13
13
|
from reflex.utils.console import deprecate
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
@@ -345,6 +345,7 @@ def format_prop(
|
|
|
345
345
|
Raises:
|
|
346
346
|
exceptions.InvalidStylePropError: If the style prop value is not a valid type.
|
|
347
347
|
TypeError: If the prop is not valid.
|
|
348
|
+
ValueError: If the prop is not a string.
|
|
348
349
|
"""
|
|
349
350
|
# import here to avoid circular import.
|
|
350
351
|
from reflex.event import EventChain
|
|
@@ -391,7 +392,8 @@ def format_prop(
|
|
|
391
392
|
raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e
|
|
392
393
|
|
|
393
394
|
# Wrap the variable in braces.
|
|
394
|
-
|
|
395
|
+
if not isinstance(prop, str):
|
|
396
|
+
raise ValueError(f"Invalid prop: {prop}. Expected a string.")
|
|
395
397
|
return wrap(prop, "{", check_first=False)
|
|
396
398
|
|
|
397
399
|
|
|
@@ -624,48 +626,6 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
|
|
|
624
626
|
return {k.replace("-", "_"): v for k, v in params.items()}
|
|
625
627
|
|
|
626
628
|
|
|
627
|
-
def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
628
|
-
"""Recursively format values in the given state.
|
|
629
|
-
|
|
630
|
-
Args:
|
|
631
|
-
value: The state to format.
|
|
632
|
-
key: The key associated with the value (optional).
|
|
633
|
-
|
|
634
|
-
Returns:
|
|
635
|
-
The formatted state.
|
|
636
|
-
|
|
637
|
-
Raises:
|
|
638
|
-
TypeError: If the given value is not a valid state.
|
|
639
|
-
"""
|
|
640
|
-
from reflex.utils import serializers
|
|
641
|
-
|
|
642
|
-
# Handle dicts.
|
|
643
|
-
if isinstance(value, dict):
|
|
644
|
-
return {k: format_state(v, k) for k, v in value.items()}
|
|
645
|
-
|
|
646
|
-
# Handle lists, sets, typles.
|
|
647
|
-
if isinstance(value, types.StateIterBases):
|
|
648
|
-
return [format_state(v) for v in value]
|
|
649
|
-
|
|
650
|
-
# Return state vars as is.
|
|
651
|
-
if isinstance(value, types.StateBases):
|
|
652
|
-
return value
|
|
653
|
-
|
|
654
|
-
# Serialize the value.
|
|
655
|
-
serialized = serializers.serialize(value)
|
|
656
|
-
if serialized is not None:
|
|
657
|
-
return serialized
|
|
658
|
-
|
|
659
|
-
if key is None:
|
|
660
|
-
raise TypeError(
|
|
661
|
-
f"No JSON serializer found for var {value} of type {type(value)}."
|
|
662
|
-
)
|
|
663
|
-
else:
|
|
664
|
-
raise TypeError(
|
|
665
|
-
f"No JSON serializer found for State Var '{key}' of value {value} of type {type(value)}."
|
|
666
|
-
)
|
|
667
|
-
|
|
668
|
-
|
|
669
629
|
def format_state_name(state_name: str) -> str:
|
|
670
630
|
"""Format a state name, replacing dots with double underscore.
|
|
671
631
|
|
|
@@ -704,6 +664,8 @@ def format_library_name(library_fullname: str):
|
|
|
704
664
|
Returns:
|
|
705
665
|
The name without the @version if it was part of the name
|
|
706
666
|
"""
|
|
667
|
+
if library_fullname.startswith("https://"):
|
|
668
|
+
return library_fullname
|
|
707
669
|
lib, at, version = library_fullname.rpartition("@")
|
|
708
670
|
if not lib:
|
|
709
671
|
lib = at + version
|
reflex/utils/path_ops.py
CHANGED
|
@@ -129,6 +129,41 @@ def which(program: str | Path) -> str | Path | None:
|
|
|
129
129
|
return shutil.which(str(program))
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
def use_system_install(var_name: str) -> bool:
|
|
133
|
+
"""Check if the system install should be used.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
var_name: The name of the environment variable.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
ValueError: If the variable name is invalid.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Whether the associated env var should use the system install.
|
|
143
|
+
"""
|
|
144
|
+
if not var_name.startswith("REFLEX_USE_SYSTEM_"):
|
|
145
|
+
raise ValueError("Invalid system install variable name.")
|
|
146
|
+
return os.getenv(var_name, "").lower() in ["true", "1", "yes"]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def use_system_node() -> bool:
|
|
150
|
+
"""Check if the system node should be used.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Whether the system node should be used.
|
|
154
|
+
"""
|
|
155
|
+
return use_system_install(constants.Node.USE_SYSTEM_VAR)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def use_system_bun() -> bool:
|
|
159
|
+
"""Check if the system bun should be used.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Whether the system bun should be used.
|
|
163
|
+
"""
|
|
164
|
+
return use_system_install(constants.Bun.USE_SYSTEM_VAR)
|
|
165
|
+
|
|
166
|
+
|
|
132
167
|
def get_node_bin_path() -> str | None:
|
|
133
168
|
"""Get the node binary dir path.
|
|
134
169
|
|
|
@@ -149,7 +184,7 @@ def get_node_path() -> str | None:
|
|
|
149
184
|
The path to the node binary file.
|
|
150
185
|
"""
|
|
151
186
|
node_path = Path(constants.Node.PATH)
|
|
152
|
-
if not node_path.exists():
|
|
187
|
+
if use_system_node() or not node_path.exists():
|
|
153
188
|
return str(which("node"))
|
|
154
189
|
return str(node_path)
|
|
155
190
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -16,7 +16,7 @@ import shutil
|
|
|
16
16
|
import stat
|
|
17
17
|
import sys
|
|
18
18
|
import tempfile
|
|
19
|
-
import
|
|
19
|
+
import time
|
|
20
20
|
import zipfile
|
|
21
21
|
from datetime import datetime
|
|
22
22
|
from fileinput import FileInput
|
|
@@ -36,6 +36,7 @@ from reflex import constants, model
|
|
|
36
36
|
from reflex.compiler import templates
|
|
37
37
|
from reflex.config import Config, get_config
|
|
38
38
|
from reflex.utils import console, net, path_ops, processes
|
|
39
|
+
from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
|
|
39
40
|
from reflex.utils.format import format_library_name
|
|
40
41
|
from reflex.utils.registry import _get_best_registry
|
|
41
42
|
|
|
@@ -73,6 +74,18 @@ def get_web_dir() -> Path:
|
|
|
73
74
|
return workdir
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
def _python_version_check():
|
|
78
|
+
"""Emit deprecation warning for deprecated python versions."""
|
|
79
|
+
# Check for end-of-life python versions.
|
|
80
|
+
if sys.version_info < (3, 10):
|
|
81
|
+
console.deprecate(
|
|
82
|
+
feature_name="Support for Python 3.9 and older",
|
|
83
|
+
reason="please upgrade to Python 3.10 or newer",
|
|
84
|
+
deprecation_version="0.6.0",
|
|
85
|
+
removal_version="0.7.0",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
76
89
|
def check_latest_package_version(package_name: str):
|
|
77
90
|
"""Check if the latest version of the package is installed.
|
|
78
91
|
|
|
@@ -85,15 +98,16 @@ def check_latest_package_version(package_name: str):
|
|
|
85
98
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
86
99
|
response = net.get(url)
|
|
87
100
|
latest_version = response.json()["info"]["version"]
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
):
|
|
92
|
-
#
|
|
93
|
-
# the last_version_check_datetime is not set in reflex.json
|
|
101
|
+
if get_or_set_last_reflex_version_check_datetime():
|
|
102
|
+
# Versions were already checked and saved in reflex.json, no need to warn again
|
|
103
|
+
return
|
|
104
|
+
if version.parse(current_version) < version.parse(latest_version):
|
|
105
|
+
# Show a warning when the host version is older than PyPI version
|
|
94
106
|
console.warn(
|
|
95
107
|
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
|
|
96
108
|
)
|
|
109
|
+
# Check for depreacted python versions
|
|
110
|
+
_python_version_check()
|
|
97
111
|
except Exception:
|
|
98
112
|
pass
|
|
99
113
|
|
|
@@ -129,7 +143,7 @@ def check_node_version() -> bool:
|
|
|
129
143
|
# Compare the version numbers
|
|
130
144
|
return (
|
|
131
145
|
current_version >= version.parse(constants.Node.MIN_VERSION)
|
|
132
|
-
if constants.IS_WINDOWS
|
|
146
|
+
if constants.IS_WINDOWS or path_ops.use_system_node()
|
|
133
147
|
else current_version == version.parse(constants.Node.VERSION)
|
|
134
148
|
)
|
|
135
149
|
return False
|
|
@@ -290,7 +304,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
|
290
304
|
"""
|
|
291
305
|
app_module = get_app(reload=reload)
|
|
292
306
|
app = getattr(app_module, constants.CompileVars.APP)
|
|
293
|
-
# For py3.
|
|
307
|
+
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
294
308
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
295
309
|
app._apply_decorated_pages()
|
|
296
310
|
app._compile(export=export)
|
|
@@ -1020,6 +1034,8 @@ def validate_bun():
|
|
|
1020
1034
|
# if a custom bun path is provided, make sure its valid
|
|
1021
1035
|
# This is specific to non-FHS OS
|
|
1022
1036
|
bun_path = get_config().bun_path
|
|
1037
|
+
if path_ops.use_system_bun():
|
|
1038
|
+
bun_path = path_ops.which("bun")
|
|
1023
1039
|
if bun_path != constants.Bun.DEFAULT_PATH:
|
|
1024
1040
|
console.info(f"Using custom Bun path: {bun_path}")
|
|
1025
1041
|
bun_version = get_bun_version()
|
|
@@ -1442,19 +1458,37 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
|
|
|
1442
1458
|
Args:
|
|
1443
1459
|
app_name: The name of the app.
|
|
1444
1460
|
generation_hash: The generation hash from reflex.build.
|
|
1461
|
+
|
|
1462
|
+
Raises:
|
|
1463
|
+
GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
|
|
1464
|
+
(the refactored reflex code is expected to have at least one root function defined).
|
|
1445
1465
|
"""
|
|
1446
1466
|
# Download the reflex code for the generation.
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1467
|
+
url = constants.Templates.REFLEX_BUILD_CODE_URL.format(
|
|
1468
|
+
generation_hash=generation_hash
|
|
1469
|
+
)
|
|
1470
|
+
resp = net.get(url)
|
|
1471
|
+
while resp.status_code == httpx.codes.SERVICE_UNAVAILABLE:
|
|
1472
|
+
console.debug("Waiting for the code to be generated...")
|
|
1473
|
+
time.sleep(1)
|
|
1474
|
+
resp = net.get(url)
|
|
1475
|
+
resp.raise_for_status()
|
|
1476
|
+
|
|
1477
|
+
# Determine the name of the last function, which renders the generated code.
|
|
1478
|
+
defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
|
|
1479
|
+
if not defined_funcs:
|
|
1480
|
+
raise GeneratedCodeHasNoFunctionDefs(
|
|
1481
|
+
f"No function definitions found in generated code from {url!r}."
|
|
1450
1482
|
)
|
|
1451
|
-
|
|
1483
|
+
render_func_name = defined_funcs[-1]
|
|
1452
1484
|
|
|
1453
1485
|
def replace_content(_match):
|
|
1454
1486
|
return "\n".join(
|
|
1455
1487
|
[
|
|
1456
|
-
|
|
1457
|
-
|
|
1488
|
+
resp.text,
|
|
1489
|
+
"",
|
|
1490
|
+
"" "def index() -> rx.Component:",
|
|
1491
|
+
f" return {render_func_name}()",
|
|
1458
1492
|
"",
|
|
1459
1493
|
"",
|
|
1460
1494
|
],
|
|
@@ -1462,13 +1496,20 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
|
|
|
1462
1496
|
|
|
1463
1497
|
main_module_path = Path(app_name, app_name + constants.Ext.PY)
|
|
1464
1498
|
main_module_code = main_module_path.read_text()
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1499
|
+
|
|
1500
|
+
main_module_code = re.sub(
|
|
1501
|
+
r"def index\(\).*:\n([^\n]\s+.*\n+)+",
|
|
1502
|
+
replace_content,
|
|
1503
|
+
main_module_code,
|
|
1504
|
+
)
|
|
1505
|
+
# Make the app use light mode until flexgen enforces the conversion of
|
|
1506
|
+
# tailwind colors to radix colors.
|
|
1507
|
+
main_module_code = re.sub(
|
|
1508
|
+
r"app\s*=\s*rx\.App\(\s*\)",
|
|
1509
|
+
'app = rx.App(theme=rx.theme(color_mode="light"))',
|
|
1510
|
+
main_module_code,
|
|
1471
1511
|
)
|
|
1512
|
+
main_module_path.write_text(main_module_code)
|
|
1472
1513
|
|
|
1473
1514
|
|
|
1474
1515
|
def format_address_width(address_width) -> int | None:
|