reflex 0.5.0a3__py3-none-any.whl → 0.5.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/web/utils/state.js +7 -2
- reflex/app.py +68 -50
- reflex/app_module_for_backend.py +3 -0
- reflex/base.py +5 -2
- reflex/components/component.py +49 -13
- reflex/components/core/__init__.py +7 -1
- reflex/components/core/banner.py +79 -6
- reflex/components/core/banner.pyi +130 -0
- reflex/components/core/cond.py +10 -4
- reflex/components/core/debounce.py +2 -4
- reflex/components/core/foreach.py +11 -0
- reflex/components/core/upload.py +9 -10
- reflex/components/el/elements/forms.py +12 -6
- reflex/components/el/elements/media.py +19 -0
- reflex/components/el/elements/media.pyi +3 -1
- reflex/components/gridjs/datatable.py +4 -2
- reflex/components/props.py +30 -0
- reflex/components/radix/themes/components/tabs.py +1 -1
- reflex/components/sonner/toast.py +102 -35
- reflex/components/sonner/toast.pyi +27 -14
- reflex/config.py +5 -3
- reflex/constants/compiler.py +3 -3
- reflex/constants/installer.py +1 -1
- reflex/event.py +38 -24
- reflex/experimental/__init__.py +4 -0
- reflex/experimental/client_state.py +198 -0
- reflex/state.py +59 -21
- reflex/style.py +3 -3
- reflex/testing.py +28 -9
- reflex/utils/exceptions.py +64 -8
- reflex/utils/format.py +73 -2
- reflex/utils/prerequisites.py +68 -23
- reflex/utils/processes.py +34 -4
- reflex/utils/telemetry.py +42 -16
- reflex/utils/types.py +16 -0
- reflex/vars.py +104 -61
- reflex/vars.pyi +7 -6
- {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/METADATA +1 -1
- {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/RECORD +42 -40
- {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/LICENSE +0 -0
- {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/WHEEL +0 -0
- {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/entry_points.txt +0 -0
reflex/state.py
CHANGED
|
@@ -279,11 +279,13 @@ class EventHandlerSetVar(EventHandler):
|
|
|
279
279
|
|
|
280
280
|
Raises:
|
|
281
281
|
AttributeError: If the given Var name does not exist on the state.
|
|
282
|
-
|
|
282
|
+
EventHandlerValueError: If the given Var name is not a str
|
|
283
283
|
"""
|
|
284
|
+
from reflex.utils.exceptions import EventHandlerValueError
|
|
285
|
+
|
|
284
286
|
if args:
|
|
285
287
|
if not isinstance(args[0], str):
|
|
286
|
-
raise
|
|
288
|
+
raise EventHandlerValueError(
|
|
287
289
|
f"Var name must be passed as a string, got {args[0]!r}"
|
|
288
290
|
)
|
|
289
291
|
# Check that the requested Var setter exists on the State at compile time.
|
|
@@ -357,6 +359,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
357
359
|
# Whether the state has ever been touched since instantiation.
|
|
358
360
|
_was_touched: bool = False
|
|
359
361
|
|
|
362
|
+
# Whether this state class is a mixin and should not be instantiated.
|
|
363
|
+
_mixin: ClassVar[bool] = False
|
|
364
|
+
|
|
360
365
|
# A special event handler for setting base vars.
|
|
361
366
|
setvar: ClassVar[EventHandler]
|
|
362
367
|
|
|
@@ -380,10 +385,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
380
385
|
**kwargs: The kwargs to pass to the Pydantic init method.
|
|
381
386
|
|
|
382
387
|
Raises:
|
|
383
|
-
|
|
388
|
+
ReflexRuntimeError: If the state is instantiated directly by end user.
|
|
384
389
|
"""
|
|
390
|
+
from reflex.utils.exceptions import ReflexRuntimeError
|
|
391
|
+
|
|
385
392
|
if not _reflex_internal_init and not is_testing_env():
|
|
386
|
-
raise
|
|
393
|
+
raise ReflexRuntimeError(
|
|
387
394
|
"State classes should not be instantiated directly in a Reflex app. "
|
|
388
395
|
"See https://reflex.dev/docs/state/ for further information."
|
|
389
396
|
)
|
|
@@ -424,23 +431,30 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
424
431
|
"""
|
|
425
432
|
return [
|
|
426
433
|
v
|
|
427
|
-
for mixin in cls.
|
|
428
|
-
if mixin is cls or not issubclass(mixin, (BaseState, ABC))
|
|
434
|
+
for mixin in cls._mixins() + [cls]
|
|
429
435
|
for v in mixin.__dict__.values()
|
|
430
436
|
if isinstance(v, ComputedVar)
|
|
431
437
|
]
|
|
432
438
|
|
|
433
439
|
@classmethod
|
|
434
|
-
def __init_subclass__(cls, **kwargs):
|
|
440
|
+
def __init_subclass__(cls, mixin: bool = False, **kwargs):
|
|
435
441
|
"""Do some magic for the subclass initialization.
|
|
436
442
|
|
|
437
443
|
Args:
|
|
444
|
+
mixin: Whether the subclass is a mixin and should not be initialized.
|
|
438
445
|
**kwargs: The kwargs to pass to the pydantic init_subclass method.
|
|
439
446
|
|
|
440
447
|
Raises:
|
|
441
|
-
|
|
448
|
+
StateValueError: If a substate class shadows another.
|
|
442
449
|
"""
|
|
450
|
+
from reflex.utils.exceptions import StateValueError
|
|
451
|
+
|
|
443
452
|
super().__init_subclass__(**kwargs)
|
|
453
|
+
|
|
454
|
+
cls._mixin = mixin
|
|
455
|
+
if mixin:
|
|
456
|
+
return
|
|
457
|
+
|
|
444
458
|
# Event handlers should not shadow builtin state methods.
|
|
445
459
|
cls._check_overridden_methods()
|
|
446
460
|
# Computed vars should not shadow builtin state props.
|
|
@@ -471,7 +485,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
471
485
|
else:
|
|
472
486
|
# During normal operation, subclasses cannot have the same name, even if they are
|
|
473
487
|
# defined in different modules.
|
|
474
|
-
raise
|
|
488
|
+
raise StateValueError(
|
|
475
489
|
f"The substate class '{cls.__name__}' has been defined multiple times. "
|
|
476
490
|
"Shadowing substate classes is not allowed."
|
|
477
491
|
)
|
|
@@ -536,7 +550,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
536
550
|
for name, value in mixin.__dict__.items():
|
|
537
551
|
if isinstance(value, ComputedVar):
|
|
538
552
|
fget = cls._copy_fn(value.fget)
|
|
539
|
-
newcv =
|
|
553
|
+
newcv = value._replace(fget=fget)
|
|
554
|
+
# cleanup refs to mixin cls in var_data
|
|
555
|
+
newcv._var_data = None
|
|
540
556
|
newcv._var_set_state(cls)
|
|
541
557
|
setattr(cls, name, newcv)
|
|
542
558
|
cls.computed_vars[newcv._var_name] = newcv
|
|
@@ -612,8 +628,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
612
628
|
return [
|
|
613
629
|
mixin
|
|
614
630
|
for mixin in cls.__mro__
|
|
615
|
-
if
|
|
616
|
-
|
|
631
|
+
if (
|
|
632
|
+
mixin not in [pydantic.BaseModel, Base, cls]
|
|
633
|
+
and issubclass(mixin, BaseState)
|
|
634
|
+
and mixin._mixin is True
|
|
635
|
+
)
|
|
617
636
|
]
|
|
618
637
|
|
|
619
638
|
@classmethod
|
|
@@ -736,7 +755,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
736
755
|
parent_states = [
|
|
737
756
|
base
|
|
738
757
|
for base in cls.__bases__
|
|
739
|
-
if
|
|
758
|
+
if issubclass(base, BaseState) and base is not BaseState and not base._mixin
|
|
740
759
|
]
|
|
741
760
|
assert len(parent_states) < 2, "Only one parent state is allowed."
|
|
742
761
|
return parent_states[0] if len(parent_states) == 1 else None # type: ignore
|
|
@@ -829,10 +848,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
829
848
|
prop: The variable to initialize
|
|
830
849
|
|
|
831
850
|
Raises:
|
|
832
|
-
|
|
851
|
+
VarTypeError: if the variable has an incorrect type
|
|
833
852
|
"""
|
|
853
|
+
from reflex.utils.exceptions import VarTypeError
|
|
854
|
+
|
|
834
855
|
if not types.is_valid_var_type(prop._var_type):
|
|
835
|
-
raise
|
|
856
|
+
raise VarTypeError(
|
|
836
857
|
"State vars must be primitive Python types, "
|
|
837
858
|
"Plotly figures, Pandas dataframes, "
|
|
838
859
|
"or subclasses of rx.Base. "
|
|
@@ -1454,6 +1475,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1454
1475
|
Yields:
|
|
1455
1476
|
StateUpdate object
|
|
1456
1477
|
"""
|
|
1478
|
+
from reflex.utils import telemetry
|
|
1479
|
+
|
|
1457
1480
|
# Get the function to process the event.
|
|
1458
1481
|
fn = functools.partial(handler.fn, state)
|
|
1459
1482
|
|
|
@@ -1489,9 +1512,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1489
1512
|
yield state._as_state_update(handler, events, final=True)
|
|
1490
1513
|
|
|
1491
1514
|
# If an error occurs, throw a window alert.
|
|
1492
|
-
except Exception:
|
|
1515
|
+
except Exception as ex:
|
|
1493
1516
|
error = traceback.format_exc()
|
|
1494
1517
|
print(error)
|
|
1518
|
+
telemetry.send_error(ex, context="backend")
|
|
1495
1519
|
yield state._as_state_update(
|
|
1496
1520
|
handler,
|
|
1497
1521
|
window_alert("An error occurred. See logs for details."),
|
|
@@ -1688,10 +1712,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1688
1712
|
if initial:
|
|
1689
1713
|
computed_vars = {
|
|
1690
1714
|
# Include initial computed vars.
|
|
1691
|
-
prop_name:
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1715
|
+
prop_name: (
|
|
1716
|
+
cv._initial_value
|
|
1717
|
+
if isinstance(cv, ComputedVar)
|
|
1718
|
+
and not isinstance(cv._initial_value, types.Unset)
|
|
1719
|
+
else self.get_value(getattr(self, prop_name))
|
|
1720
|
+
)
|
|
1695
1721
|
for prop_name, cv in self.computed_vars.items()
|
|
1696
1722
|
}
|
|
1697
1723
|
elif include_computed:
|
|
@@ -1818,7 +1844,7 @@ class OnLoadInternalState(State):
|
|
|
1818
1844
|
]
|
|
1819
1845
|
|
|
1820
1846
|
|
|
1821
|
-
class ComponentState(
|
|
1847
|
+
class ComponentState(State, mixin=True):
|
|
1822
1848
|
"""Base class to allow for the creation of a state instance per component.
|
|
1823
1849
|
|
|
1824
1850
|
This allows for the bundling of UI and state logic into a single class,
|
|
@@ -1860,6 +1886,18 @@ class ComponentState(Base):
|
|
|
1860
1886
|
# The number of components created from this class.
|
|
1861
1887
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
1862
1888
|
|
|
1889
|
+
@classmethod
|
|
1890
|
+
def __init_subclass__(cls, mixin: bool = False, **kwargs):
|
|
1891
|
+
"""Overwrite mixin default to True.
|
|
1892
|
+
|
|
1893
|
+
Args:
|
|
1894
|
+
mixin: Whether the subclass is a mixin and should not be initialized.
|
|
1895
|
+
**kwargs: The kwargs to pass to the pydantic init_subclass method.
|
|
1896
|
+
"""
|
|
1897
|
+
if ComponentState in cls.__bases__:
|
|
1898
|
+
mixin = True
|
|
1899
|
+
super().__init_subclass__(mixin=mixin, **kwargs)
|
|
1900
|
+
|
|
1863
1901
|
@classmethod
|
|
1864
1902
|
def get_component(cls, *children, **props) -> "Component":
|
|
1865
1903
|
"""Get the component instance.
|
reflex/style.py
CHANGED
|
@@ -16,10 +16,10 @@ LIGHT_COLOR_MODE: str = "light"
|
|
|
16
16
|
DARK_COLOR_MODE: str = "dark"
|
|
17
17
|
|
|
18
18
|
# Reference the global ColorModeContext
|
|
19
|
-
color_mode_var_data = VarData(
|
|
19
|
+
color_mode_var_data = VarData(
|
|
20
20
|
imports={
|
|
21
|
-
f"/{constants.Dirs.CONTEXTS_PATH}":
|
|
22
|
-
"react":
|
|
21
|
+
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
|
|
22
|
+
"react": [ImportVar(tag="useContext")],
|
|
23
23
|
},
|
|
24
24
|
hooks={
|
|
25
25
|
f"const [ {constants.ColorMode.NAME}, {constants.ColorMode.TOGGLE} ] = useContext(ColorModeContext)": None,
|
reflex/testing.py
CHANGED
|
@@ -26,6 +26,7 @@ from typing import (
|
|
|
26
26
|
AsyncIterator,
|
|
27
27
|
Callable,
|
|
28
28
|
Coroutine,
|
|
29
|
+
List,
|
|
29
30
|
Optional,
|
|
30
31
|
Type,
|
|
31
32
|
TypeVar,
|
|
@@ -513,12 +514,19 @@ class AppHarness:
|
|
|
513
514
|
raise TimeoutError("Backend is not listening.")
|
|
514
515
|
return backend.servers[0].sockets[0]
|
|
515
516
|
|
|
516
|
-
def frontend(
|
|
517
|
+
def frontend(
|
|
518
|
+
self,
|
|
519
|
+
driver_clz: Optional[Type["WebDriver"]] = None,
|
|
520
|
+
driver_kwargs: dict[str, Any] | None = None,
|
|
521
|
+
driver_option_args: List[str] | None = None,
|
|
522
|
+
) -> "WebDriver":
|
|
517
523
|
"""Get a selenium webdriver instance pointed at the app.
|
|
518
524
|
|
|
519
525
|
Args:
|
|
520
526
|
driver_clz: webdriver.Chrome (default), webdriver.Firefox, webdriver.Safari,
|
|
521
527
|
webdriver.Edge, etc
|
|
528
|
+
driver_kwargs: additional keyword arguments to pass to the webdriver constructor
|
|
529
|
+
driver_option_args: additional arguments for the webdriver options
|
|
522
530
|
|
|
523
531
|
Returns:
|
|
524
532
|
Instance of the given webdriver navigated to the frontend url of the app.
|
|
@@ -541,19 +549,30 @@ class AppHarness:
|
|
|
541
549
|
requested_driver = os.environ.get("APP_HARNESS_DRIVER", "Chrome")
|
|
542
550
|
driver_clz = getattr(webdriver, requested_driver)
|
|
543
551
|
options = getattr(webdriver, f"{requested_driver}Options")()
|
|
544
|
-
if driver_clz is webdriver.Chrome
|
|
552
|
+
if driver_clz is webdriver.Chrome:
|
|
545
553
|
options = webdriver.ChromeOptions()
|
|
546
|
-
options.add_argument("--
|
|
547
|
-
|
|
554
|
+
options.add_argument("--class=AppHarness")
|
|
555
|
+
if want_headless:
|
|
556
|
+
options.add_argument("--headless=new")
|
|
557
|
+
elif driver_clz is webdriver.Firefox:
|
|
548
558
|
options = webdriver.FirefoxOptions()
|
|
549
|
-
|
|
550
|
-
|
|
559
|
+
if want_headless:
|
|
560
|
+
options.add_argument("-headless")
|
|
561
|
+
elif driver_clz is webdriver.Edge:
|
|
551
562
|
options = webdriver.EdgeOptions()
|
|
552
|
-
|
|
553
|
-
|
|
563
|
+
if want_headless:
|
|
564
|
+
options.add_argument("headless")
|
|
565
|
+
if options is None:
|
|
566
|
+
raise RuntimeError(f"Could not determine options for {driver_clz}")
|
|
567
|
+
if args := os.environ.get("APP_HARNESS_DRIVER_ARGS"):
|
|
554
568
|
for arg in args.split(","):
|
|
555
569
|
options.add_argument(arg)
|
|
556
|
-
|
|
570
|
+
if driver_option_args is not None:
|
|
571
|
+
for arg in driver_option_args:
|
|
572
|
+
options.add_argument(arg)
|
|
573
|
+
if driver_kwargs is None:
|
|
574
|
+
driver_kwargs = {}
|
|
575
|
+
driver = driver_clz(options=options, **driver_kwargs) # type: ignore
|
|
557
576
|
driver.get(self.frontend_url)
|
|
558
577
|
self._frontends.append(driver)
|
|
559
578
|
return driver
|
reflex/utils/exceptions.py
CHANGED
|
@@ -1,21 +1,77 @@
|
|
|
1
1
|
"""Custom Exceptions."""
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class
|
|
5
|
-
"""
|
|
4
|
+
class ReflexError(Exception):
|
|
5
|
+
"""Base exception for all Reflex exceptions."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReflexRuntimeError(ReflexError, RuntimeError):
|
|
9
|
+
"""Custom RuntimeError for Reflex."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UploadTypeError(ReflexError, TypeError):
|
|
13
|
+
"""Custom TypeError for upload related errors."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EnvVarValueError(ReflexError, ValueError):
|
|
17
|
+
"""Custom ValueError raised when unable to convert env var to expected type."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ComponentTypeError(ReflexError, TypeError):
|
|
21
|
+
"""Custom TypeError for component related errors."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EventHandlerTypeError(ReflexError, TypeError):
|
|
25
|
+
"""Custom TypeError for event handler related errors."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EventHandlerValueError(ReflexError, ValueError):
|
|
29
|
+
"""Custom ValueError for event handler related errors."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StateValueError(ReflexError, ValueError):
|
|
33
|
+
"""Custom ValueError for state related errors."""
|
|
34
|
+
|
|
6
35
|
|
|
7
|
-
|
|
36
|
+
class VarNameError(ReflexError, NameError):
|
|
37
|
+
"""Custom NameError for when a state var has been shadowed by a substate var."""
|
|
8
38
|
|
|
9
39
|
|
|
10
|
-
class
|
|
40
|
+
class VarTypeError(ReflexError, TypeError):
|
|
41
|
+
"""Custom TypeError for var related errors."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VarValueError(ReflexError, ValueError):
|
|
45
|
+
"""Custom ValueError for var related errors."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class VarAttributeError(ReflexError, AttributeError):
|
|
49
|
+
"""Custom AttributeError for var related errors."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class UploadValueError(ReflexError, ValueError):
|
|
53
|
+
"""Custom ValueError for upload related errors."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RouteValueError(ReflexError, ValueError):
|
|
57
|
+
"""Custom ValueError for route related errors."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class VarOperationTypeError(ReflexError, TypeError):
|
|
61
|
+
"""Custom TypeError for when unsupported operations are performed on vars."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class InvalidStylePropError(ReflexError, TypeError):
|
|
65
|
+
"""Custom Type Error when style props have invalid values."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ImmutableStateError(ReflexError):
|
|
11
69
|
"""Raised when a background task attempts to modify state outside of context."""
|
|
12
70
|
|
|
13
71
|
|
|
14
|
-
class LockExpiredError(
|
|
72
|
+
class LockExpiredError(ReflexError):
|
|
15
73
|
"""Raised when the state lock expires while an event is being processed."""
|
|
16
74
|
|
|
17
75
|
|
|
18
|
-
class MatchTypeError(TypeError):
|
|
76
|
+
class MatchTypeError(ReflexError, TypeError):
|
|
19
77
|
"""Raised when the return types of match cases are different."""
|
|
20
|
-
|
|
21
|
-
pass
|
reflex/utils/format.py
CHANGED
|
@@ -6,7 +6,7 @@ import inspect
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
|
-
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
10
10
|
|
|
11
11
|
from reflex import constants
|
|
12
12
|
from reflex.utils import exceptions, serializers, types
|
|
@@ -15,7 +15,7 @@ from reflex.vars import BaseVar, Var
|
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from reflex.components.component import ComponentStyle
|
|
18
|
-
from reflex.event import EventChain, EventHandler, EventSpec
|
|
18
|
+
from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec
|
|
19
19
|
|
|
20
20
|
WRAP_MAP = {
|
|
21
21
|
"{": "}",
|
|
@@ -590,6 +590,77 @@ def format_event_chain(
|
|
|
590
590
|
)
|
|
591
591
|
|
|
592
592
|
|
|
593
|
+
def format_queue_events(
|
|
594
|
+
events: EventSpec
|
|
595
|
+
| EventHandler
|
|
596
|
+
| Callable
|
|
597
|
+
| List[EventSpec | EventHandler | Callable]
|
|
598
|
+
| None = None,
|
|
599
|
+
args_spec: Optional[ArgsSpec] = None,
|
|
600
|
+
) -> Var[EventChain]:
|
|
601
|
+
"""Format a list of event handler / event spec as a javascript callback.
|
|
602
|
+
|
|
603
|
+
The resulting code can be passed to interfaces that expect a callback
|
|
604
|
+
function and when triggered it will directly call queueEvents.
|
|
605
|
+
|
|
606
|
+
It is intended to be executed in the rx.call_script context, where some
|
|
607
|
+
existing API needs a callback to trigger a backend event handler.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
events: The events to queue.
|
|
611
|
+
args_spec: The argument spec for the callback.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
The compiled javascript callback to queue the given events on the frontend.
|
|
615
|
+
"""
|
|
616
|
+
from reflex.event import (
|
|
617
|
+
EventChain,
|
|
618
|
+
EventHandler,
|
|
619
|
+
EventSpec,
|
|
620
|
+
call_event_fn,
|
|
621
|
+
call_event_handler,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
if not events:
|
|
625
|
+
return Var.create_safe(
|
|
626
|
+
"() => null", _var_is_string=False, _var_is_local=False
|
|
627
|
+
).to(EventChain)
|
|
628
|
+
|
|
629
|
+
# If no spec is provided, the function will take no arguments.
|
|
630
|
+
def _default_args_spec():
|
|
631
|
+
return []
|
|
632
|
+
|
|
633
|
+
# Construct the arguments that the function accepts.
|
|
634
|
+
sig = inspect.signature(args_spec or _default_args_spec) # type: ignore
|
|
635
|
+
if sig.parameters:
|
|
636
|
+
arg_def = ",".join(f"_{p}" for p in sig.parameters)
|
|
637
|
+
arg_def = f"({arg_def})"
|
|
638
|
+
else:
|
|
639
|
+
arg_def = "()"
|
|
640
|
+
|
|
641
|
+
payloads = []
|
|
642
|
+
if not isinstance(events, list):
|
|
643
|
+
events = [events]
|
|
644
|
+
|
|
645
|
+
# Process each event/spec/lambda (similar to Component._create_event_chain).
|
|
646
|
+
for spec in events:
|
|
647
|
+
specs: list[EventSpec] = []
|
|
648
|
+
if isinstance(spec, (EventHandler, EventSpec)):
|
|
649
|
+
specs = [call_event_handler(spec, args_spec or _default_args_spec)]
|
|
650
|
+
elif isinstance(spec, type(lambda: None)):
|
|
651
|
+
specs = call_event_fn(spec, args_spec or _default_args_spec)
|
|
652
|
+
payloads.extend(format_event(s) for s in specs)
|
|
653
|
+
|
|
654
|
+
# Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope.
|
|
655
|
+
# Typically this snippet will _only_ run from within an rx.call_script eval context.
|
|
656
|
+
return Var.create_safe(
|
|
657
|
+
f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}); "
|
|
658
|
+
f"processEvent({constants.CompileVars.SOCKET})}}",
|
|
659
|
+
_var_is_string=False,
|
|
660
|
+
_var_is_local=False,
|
|
661
|
+
).to(EventChain)
|
|
662
|
+
|
|
663
|
+
|
|
593
664
|
def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
|
|
594
665
|
"""Convert back query params name to python-friendly case.
|
|
595
666
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -181,7 +181,12 @@ def get_install_package_manager() -> str | None:
|
|
|
181
181
|
Returns:
|
|
182
182
|
The path to the package manager.
|
|
183
183
|
"""
|
|
184
|
-
if
|
|
184
|
+
if (
|
|
185
|
+
constants.IS_WINDOWS
|
|
186
|
+
and not is_windows_bun_supported()
|
|
187
|
+
or windows_check_onedrive_in_path()
|
|
188
|
+
or windows_npm_escape_hatch()
|
|
189
|
+
):
|
|
185
190
|
return get_package_manager()
|
|
186
191
|
return get_config().bun_path
|
|
187
192
|
|
|
@@ -199,6 +204,24 @@ def get_package_manager() -> str | None:
|
|
|
199
204
|
return npm_path
|
|
200
205
|
|
|
201
206
|
|
|
207
|
+
def windows_check_onedrive_in_path() -> bool:
|
|
208
|
+
"""For windows, check if oneDrive is present in the project dir path.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
If oneDrive is in the path of the project directory.
|
|
212
|
+
"""
|
|
213
|
+
return "onedrive" in str(Path.cwd()).lower()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def windows_npm_escape_hatch() -> bool:
|
|
217
|
+
"""For windows, if the user sets REFLEX_USE_NPM, use npm instead of bun.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
If the user has set REFLEX_USE_NPM.
|
|
221
|
+
"""
|
|
222
|
+
return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"]
|
|
223
|
+
|
|
224
|
+
|
|
202
225
|
def get_app(reload: bool = False) -> ModuleType:
|
|
203
226
|
"""Get the app module based on the default config.
|
|
204
227
|
|
|
@@ -211,27 +234,33 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
211
234
|
Raises:
|
|
212
235
|
RuntimeError: If the app name is not set in the config.
|
|
213
236
|
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
from reflex.utils import telemetry
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
os.environ[constants.RELOAD_CONFIG] = str(reload)
|
|
241
|
+
config = get_config()
|
|
242
|
+
if not config.app_name:
|
|
243
|
+
raise RuntimeError(
|
|
244
|
+
"Cannot get the app module because `app_name` is not set in rxconfig! "
|
|
245
|
+
"If this error occurs in a reflex test case, ensure that `get_app` is mocked."
|
|
246
|
+
)
|
|
247
|
+
module = config.module
|
|
248
|
+
sys.path.insert(0, os.getcwd())
|
|
249
|
+
app = __import__(module, fromlist=(constants.CompileVars.APP,))
|
|
224
250
|
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
if reload:
|
|
252
|
+
from reflex.state import reload_state_module
|
|
227
253
|
|
|
228
|
-
|
|
229
|
-
|
|
254
|
+
# Reset rx.State subclasses to avoid conflict when reloading.
|
|
255
|
+
reload_state_module(module=module)
|
|
230
256
|
|
|
231
|
-
|
|
232
|
-
|
|
257
|
+
# Reload the app module.
|
|
258
|
+
importlib.reload(app)
|
|
233
259
|
|
|
234
|
-
|
|
260
|
+
return app
|
|
261
|
+
except Exception as ex:
|
|
262
|
+
telemetry.send_error(ex, context="frontend")
|
|
263
|
+
raise
|
|
235
264
|
|
|
236
265
|
|
|
237
266
|
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
@@ -383,7 +412,7 @@ def initialize_gitignore(
|
|
|
383
412
|
# Write files to the .gitignore file.
|
|
384
413
|
with open(gitignore_file, "w", newline="\n") as f:
|
|
385
414
|
console.debug(f"Creating {gitignore_file}")
|
|
386
|
-
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}")
|
|
415
|
+
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}\n")
|
|
387
416
|
|
|
388
417
|
|
|
389
418
|
def initialize_requirements_txt():
|
|
@@ -737,10 +766,17 @@ def install_bun():
|
|
|
737
766
|
Raises:
|
|
738
767
|
FileNotFoundError: If required packages are not found.
|
|
739
768
|
"""
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
769
|
+
win_supported = is_windows_bun_supported()
|
|
770
|
+
one_drive_in_path = windows_check_onedrive_in_path()
|
|
771
|
+
if constants.IS_WINDOWS and not win_supported or one_drive_in_path:
|
|
772
|
+
if not win_supported:
|
|
773
|
+
console.warn(
|
|
774
|
+
"Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm."
|
|
775
|
+
)
|
|
776
|
+
if one_drive_in_path:
|
|
777
|
+
console.warn(
|
|
778
|
+
"Creating project directories in OneDrive is not recommended for bun usage on windows. This will fallback to npm."
|
|
779
|
+
)
|
|
744
780
|
|
|
745
781
|
# Skip if bun is already installed.
|
|
746
782
|
if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse(
|
|
@@ -843,11 +879,13 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
843
879
|
if not constants.IS_WINDOWS
|
|
844
880
|
or constants.IS_WINDOWS
|
|
845
881
|
and is_windows_bun_supported()
|
|
882
|
+
and not windows_check_onedrive_in_path()
|
|
846
883
|
else None
|
|
847
884
|
)
|
|
848
885
|
processes.run_process_with_fallback(
|
|
849
886
|
[get_install_package_manager(), "install"], # type: ignore
|
|
850
887
|
fallback=fallback_command,
|
|
888
|
+
analytics_enabled=True,
|
|
851
889
|
show_status_message="Installing base frontend packages",
|
|
852
890
|
cwd=constants.Dirs.WEB,
|
|
853
891
|
shell=constants.IS_WINDOWS,
|
|
@@ -863,6 +901,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
863
901
|
*((config.tailwind or {}).get("plugins", [])),
|
|
864
902
|
],
|
|
865
903
|
fallback=fallback_command,
|
|
904
|
+
analytics_enabled=True,
|
|
866
905
|
show_status_message="Installing tailwind",
|
|
867
906
|
cwd=constants.Dirs.WEB,
|
|
868
907
|
shell=constants.IS_WINDOWS,
|
|
@@ -873,6 +912,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
873
912
|
processes.run_process_with_fallback(
|
|
874
913
|
[get_install_package_manager(), "add", *packages],
|
|
875
914
|
fallback=fallback_command,
|
|
915
|
+
analytics_enabled=True,
|
|
876
916
|
show_status_message="Installing frontend packages from config and components",
|
|
877
917
|
cwd=constants.Dirs.WEB,
|
|
878
918
|
shell=constants.IS_WINDOWS,
|
|
@@ -929,6 +969,11 @@ def needs_reinit(frontend: bool = True) -> bool:
|
|
|
929
969
|
console.warn(
|
|
930
970
|
f"""On Python < 3.12, `uvicorn==0.20.0` is recommended for improved hot reload times. Found {uvi_ver} instead."""
|
|
931
971
|
)
|
|
972
|
+
|
|
973
|
+
if windows_check_onedrive_in_path():
|
|
974
|
+
console.warn(
|
|
975
|
+
"Creating project directories in OneDrive may lead to performance issues. For optimal performance, It is recommended to avoid using OneDrive for your reflex app."
|
|
976
|
+
)
|
|
932
977
|
# No need to reinitialize if the app is already initialized.
|
|
933
978
|
return False
|
|
934
979
|
|