reflex 0.5.0.post1__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 +27 -18
- 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.0.post1.dist-info → reflex-0.5.1.dist-info}/METADATA +1 -1
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1.dist-info}/RECORD +42 -40
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1.dist-info}/LICENSE +0 -0
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1.dist-info}/WHEEL +0 -0
- {reflex-0.5.0.post1.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
|
@@ -234,27 +234,33 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
234
234
|
Raises:
|
|
235
235
|
RuntimeError: If the app name is not set in the config.
|
|
236
236
|
"""
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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,))
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
if reload:
|
|
252
|
+
from reflex.state import reload_state_module
|
|
250
253
|
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
# Reset rx.State subclasses to avoid conflict when reloading.
|
|
255
|
+
reload_state_module(module=module)
|
|
253
256
|
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
# Reload the app module.
|
|
258
|
+
importlib.reload(app)
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
return app
|
|
261
|
+
except Exception as ex:
|
|
262
|
+
telemetry.send_error(ex, context="frontend")
|
|
263
|
+
raise
|
|
258
264
|
|
|
259
265
|
|
|
260
266
|
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
@@ -406,7 +412,7 @@ def initialize_gitignore(
|
|
|
406
412
|
# Write files to the .gitignore file.
|
|
407
413
|
with open(gitignore_file, "w", newline="\n") as f:
|
|
408
414
|
console.debug(f"Creating {gitignore_file}")
|
|
409
|
-
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}")
|
|
415
|
+
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}\n")
|
|
410
416
|
|
|
411
417
|
|
|
412
418
|
def initialize_requirements_txt():
|
|
@@ -879,6 +885,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
879
885
|
processes.run_process_with_fallback(
|
|
880
886
|
[get_install_package_manager(), "install"], # type: ignore
|
|
881
887
|
fallback=fallback_command,
|
|
888
|
+
analytics_enabled=True,
|
|
882
889
|
show_status_message="Installing base frontend packages",
|
|
883
890
|
cwd=constants.Dirs.WEB,
|
|
884
891
|
shell=constants.IS_WINDOWS,
|
|
@@ -894,6 +901,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
894
901
|
*((config.tailwind or {}).get("plugins", [])),
|
|
895
902
|
],
|
|
896
903
|
fallback=fallback_command,
|
|
904
|
+
analytics_enabled=True,
|
|
897
905
|
show_status_message="Installing tailwind",
|
|
898
906
|
cwd=constants.Dirs.WEB,
|
|
899
907
|
shell=constants.IS_WINDOWS,
|
|
@@ -904,6 +912,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
904
912
|
processes.run_process_with_fallback(
|
|
905
913
|
[get_install_package_manager(), "add", *packages],
|
|
906
914
|
fallback=fallback_command,
|
|
915
|
+
analytics_enabled=True,
|
|
907
916
|
show_status_message="Installing frontend packages from config and components",
|
|
908
917
|
cwd=constants.Dirs.WEB,
|
|
909
918
|
shell=constants.IS_WINDOWS,
|
reflex/utils/processes.py
CHANGED
|
@@ -211,6 +211,7 @@ def stream_logs(
|
|
|
211
211
|
process: subprocess.Popen,
|
|
212
212
|
progress=None,
|
|
213
213
|
suppress_errors: bool = False,
|
|
214
|
+
analytics_enabled: bool = False,
|
|
214
215
|
):
|
|
215
216
|
"""Stream the logs for a process.
|
|
216
217
|
|
|
@@ -219,6 +220,7 @@ def stream_logs(
|
|
|
219
220
|
process: The process.
|
|
220
221
|
progress: The ongoing progress bar if one is being used.
|
|
221
222
|
suppress_errors: If True, do not exit if errors are encountered (for fallback).
|
|
223
|
+
analytics_enabled: Whether analytics are enabled for this command.
|
|
222
224
|
|
|
223
225
|
Yields:
|
|
224
226
|
The lines of the process output.
|
|
@@ -226,6 +228,8 @@ def stream_logs(
|
|
|
226
228
|
Raises:
|
|
227
229
|
Exit: If the process failed.
|
|
228
230
|
"""
|
|
231
|
+
from reflex.utils import telemetry
|
|
232
|
+
|
|
229
233
|
# Store the tail of the logs.
|
|
230
234
|
logs = collections.deque(maxlen=512)
|
|
231
235
|
with process:
|
|
@@ -246,6 +250,8 @@ def stream_logs(
|
|
|
246
250
|
console.error(f"{message} failed with exit code {process.returncode}")
|
|
247
251
|
for line in logs:
|
|
248
252
|
console.error(line, end="")
|
|
253
|
+
if analytics_enabled:
|
|
254
|
+
telemetry.send("error", context=message)
|
|
249
255
|
console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
|
|
250
256
|
raise typer.Exit(1)
|
|
251
257
|
|
|
@@ -261,16 +267,27 @@ def show_logs(message: str, process: subprocess.Popen):
|
|
|
261
267
|
pass
|
|
262
268
|
|
|
263
269
|
|
|
264
|
-
def show_status(
|
|
270
|
+
def show_status(
|
|
271
|
+
message: str,
|
|
272
|
+
process: subprocess.Popen,
|
|
273
|
+
suppress_errors: bool = False,
|
|
274
|
+
analytics_enabled: bool = False,
|
|
275
|
+
):
|
|
265
276
|
"""Show the status of a process.
|
|
266
277
|
|
|
267
278
|
Args:
|
|
268
279
|
message: The initial message to display.
|
|
269
280
|
process: The process.
|
|
270
281
|
suppress_errors: If True, do not exit if errors are encountered (for fallback).
|
|
282
|
+
analytics_enabled: Whether analytics are enabled for this command.
|
|
271
283
|
"""
|
|
272
284
|
with console.status(message) as status:
|
|
273
|
-
for line in stream_logs(
|
|
285
|
+
for line in stream_logs(
|
|
286
|
+
message,
|
|
287
|
+
process,
|
|
288
|
+
suppress_errors=suppress_errors,
|
|
289
|
+
analytics_enabled=analytics_enabled,
|
|
290
|
+
):
|
|
274
291
|
status.update(f"{message} {line}")
|
|
275
292
|
|
|
276
293
|
|
|
@@ -319,19 +336,31 @@ def get_command_with_loglevel(command: list[str]) -> list[str]:
|
|
|
319
336
|
return command
|
|
320
337
|
|
|
321
338
|
|
|
322
|
-
def run_process_with_fallback(
|
|
339
|
+
def run_process_with_fallback(
|
|
340
|
+
args,
|
|
341
|
+
*,
|
|
342
|
+
show_status_message,
|
|
343
|
+
fallback=None,
|
|
344
|
+
analytics_enabled: bool = False,
|
|
345
|
+
**kwargs,
|
|
346
|
+
):
|
|
323
347
|
"""Run subprocess and retry using fallback command if initial command fails.
|
|
324
348
|
|
|
325
349
|
Args:
|
|
326
350
|
args: A string, or a sequence of program arguments.
|
|
327
351
|
show_status_message: The status message to be displayed in the console.
|
|
328
352
|
fallback: The fallback command to run.
|
|
353
|
+
analytics_enabled: Whether analytics are enabled for this command.
|
|
329
354
|
kwargs: Kwargs to pass to new_process function.
|
|
330
355
|
"""
|
|
331
356
|
process = new_process(get_command_with_loglevel(args), **kwargs)
|
|
332
357
|
if fallback is None:
|
|
333
358
|
# No fallback given, or this _is_ the fallback command.
|
|
334
|
-
show_status(
|
|
359
|
+
show_status(
|
|
360
|
+
show_status_message,
|
|
361
|
+
process,
|
|
362
|
+
analytics_enabled=analytics_enabled,
|
|
363
|
+
)
|
|
335
364
|
else:
|
|
336
365
|
# Suppress errors for initial command, because we will try to fallback
|
|
337
366
|
show_status(show_status_message, process, suppress_errors=True)
|
|
@@ -345,6 +374,7 @@ def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwa
|
|
|
345
374
|
fallback_args,
|
|
346
375
|
show_status_message=show_status_message,
|
|
347
376
|
fallback=None,
|
|
377
|
+
analytics_enabled=analytics_enabled,
|
|
348
378
|
**kwargs,
|
|
349
379
|
)
|
|
350
380
|
|