reflex 0.5.0.post1__py3-none-any.whl → 0.5.1a1__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 +69 -50
- 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 +1 -1
- 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 +61 -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 +28 -18
- reflex/utils/processes.py +34 -4
- reflex/utils/telemetry.py +5 -5
- 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.1a1.dist-info}/METADATA +1 -1
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/RECORD +41 -39
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/LICENSE +0 -0
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/WHEEL +0 -0
- {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/entry_points.txt +0 -0
reflex/config.py
CHANGED
|
@@ -251,8 +251,10 @@ class Config(Base):
|
|
|
251
251
|
The updated config values.
|
|
252
252
|
|
|
253
253
|
Raises:
|
|
254
|
-
|
|
254
|
+
EnvVarValueError: If an environment variable is set to an invalid type.
|
|
255
255
|
"""
|
|
256
|
+
from reflex.utils.exceptions import EnvVarValueError
|
|
257
|
+
|
|
256
258
|
updated_values = {}
|
|
257
259
|
# Iterate over the fields.
|
|
258
260
|
for key, field in self.__fields__.items():
|
|
@@ -273,11 +275,11 @@ class Config(Base):
|
|
|
273
275
|
env_var = env_var.lower() in ["true", "1", "yes"]
|
|
274
276
|
else:
|
|
275
277
|
env_var = field.type_(env_var)
|
|
276
|
-
except ValueError:
|
|
278
|
+
except ValueError as ve:
|
|
277
279
|
console.error(
|
|
278
280
|
f"Could not convert {key.upper()}={env_var} to type {field.type_}"
|
|
279
281
|
)
|
|
280
|
-
raise
|
|
282
|
+
raise EnvVarValueError from ve
|
|
281
283
|
|
|
282
284
|
# Set the value.
|
|
283
285
|
updated_values[key] = env_var
|
reflex/constants/compiler.py
CHANGED
|
@@ -103,9 +103,9 @@ class Imports(SimpleNamespace):
|
|
|
103
103
|
"""Common sets of import vars."""
|
|
104
104
|
|
|
105
105
|
EVENTS = {
|
|
106
|
-
"react":
|
|
107
|
-
f"/{Dirs.CONTEXTS_PATH}":
|
|
108
|
-
f"/{Dirs.STATE_PATH}":
|
|
106
|
+
"react": [ImportVar(tag="useContext")],
|
|
107
|
+
f"/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
|
|
108
|
+
f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
|
reflex/constants/installer.py
CHANGED
reflex/event.py
CHANGED
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
6
|
from base64 import b64encode
|
|
7
|
-
from types import FunctionType
|
|
8
7
|
from typing import (
|
|
9
8
|
Any,
|
|
10
9
|
Callable,
|
|
@@ -180,8 +179,10 @@ class EventHandler(EventActionsMixin):
|
|
|
180
179
|
The event spec, containing both the function and args.
|
|
181
180
|
|
|
182
181
|
Raises:
|
|
183
|
-
|
|
182
|
+
EventHandlerTypeError: If the arguments are invalid.
|
|
184
183
|
"""
|
|
184
|
+
from reflex.utils.exceptions import EventHandlerTypeError
|
|
185
|
+
|
|
185
186
|
# Get the function args.
|
|
186
187
|
fn_args = inspect.getfullargspec(self.fn).args[1:]
|
|
187
188
|
fn_args = (Var.create_safe(arg) for arg in fn_args)
|
|
@@ -197,7 +198,7 @@ class EventHandler(EventActionsMixin):
|
|
|
197
198
|
try:
|
|
198
199
|
values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
|
|
199
200
|
except TypeError as e:
|
|
200
|
-
raise
|
|
201
|
+
raise EventHandlerTypeError(
|
|
201
202
|
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
|
|
202
203
|
) from e
|
|
203
204
|
payload = tuple(zip(fn_args, values))
|
|
@@ -256,8 +257,10 @@ class EventSpec(EventActionsMixin):
|
|
|
256
257
|
The event spec with the new arguments.
|
|
257
258
|
|
|
258
259
|
Raises:
|
|
259
|
-
|
|
260
|
+
EventHandlerTypeError: If the arguments are invalid.
|
|
260
261
|
"""
|
|
262
|
+
from reflex.utils.exceptions import EventHandlerTypeError
|
|
263
|
+
|
|
261
264
|
# Get the remaining unfilled function args.
|
|
262
265
|
fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
|
|
263
266
|
fn_args = (Var.create_safe(arg) for arg in fn_args)
|
|
@@ -268,7 +271,7 @@ class EventSpec(EventActionsMixin):
|
|
|
268
271
|
try:
|
|
269
272
|
values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
|
|
270
273
|
except TypeError as e:
|
|
271
|
-
raise
|
|
274
|
+
raise EventHandlerTypeError(
|
|
272
275
|
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
|
|
273
276
|
) from e
|
|
274
277
|
new_payload = tuple(zip(fn_args, values))
|
|
@@ -312,10 +315,12 @@ class CallableEventSpec(EventSpec):
|
|
|
312
315
|
The EventSpec returned from calling the function.
|
|
313
316
|
|
|
314
317
|
Raises:
|
|
315
|
-
|
|
318
|
+
EventHandlerTypeError: If the CallableEventSpec has no associated function.
|
|
316
319
|
"""
|
|
320
|
+
from reflex.utils.exceptions import EventHandlerTypeError
|
|
321
|
+
|
|
317
322
|
if self.fn is None:
|
|
318
|
-
raise
|
|
323
|
+
raise EventHandlerTypeError("CallableEventSpec has no associated function.")
|
|
319
324
|
return self.fn(*args, **kwargs)
|
|
320
325
|
|
|
321
326
|
|
|
@@ -462,18 +467,27 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
|
|
|
462
467
|
)
|
|
463
468
|
|
|
464
469
|
|
|
465
|
-
def redirect(
|
|
470
|
+
def redirect(
|
|
471
|
+
path: str | Var[str],
|
|
472
|
+
external: Optional[bool] = False,
|
|
473
|
+
replace: Optional[bool] = False,
|
|
474
|
+
) -> EventSpec:
|
|
466
475
|
"""Redirect to a new path.
|
|
467
476
|
|
|
468
477
|
Args:
|
|
469
478
|
path: The path to redirect to.
|
|
470
479
|
external: Whether to open in new tab or not.
|
|
480
|
+
replace: If True, the current page will not create a new history entry.
|
|
471
481
|
|
|
472
482
|
Returns:
|
|
473
483
|
An event to redirect to the path.
|
|
474
484
|
"""
|
|
475
485
|
return server_side(
|
|
476
|
-
"_redirect",
|
|
486
|
+
"_redirect",
|
|
487
|
+
get_fn_signature(redirect),
|
|
488
|
+
path=path,
|
|
489
|
+
external=external,
|
|
490
|
+
replace=replace,
|
|
477
491
|
)
|
|
478
492
|
|
|
479
493
|
|
|
@@ -700,7 +714,11 @@ def _callback_arg_spec(eval_result):
|
|
|
700
714
|
|
|
701
715
|
def call_script(
|
|
702
716
|
javascript_code: str,
|
|
703
|
-
callback:
|
|
717
|
+
callback: EventSpec
|
|
718
|
+
| EventHandler
|
|
719
|
+
| Callable
|
|
720
|
+
| List[EventSpec | EventHandler | Callable]
|
|
721
|
+
| None = None,
|
|
704
722
|
) -> EventSpec:
|
|
705
723
|
"""Create an event handler that executes arbitrary javascript code.
|
|
706
724
|
|
|
@@ -710,21 +728,14 @@ def call_script(
|
|
|
710
728
|
|
|
711
729
|
Returns:
|
|
712
730
|
EventSpec: An event that will execute the client side javascript.
|
|
713
|
-
|
|
714
|
-
Raises:
|
|
715
|
-
ValueError: If the callback is not a valid event handler.
|
|
716
731
|
"""
|
|
717
732
|
callback_kwargs = {}
|
|
718
733
|
if callback is not None:
|
|
719
|
-
arg_name = parse_args_spec(_callback_arg_spec)[0]._var_name
|
|
720
|
-
if isinstance(callback, EventHandler):
|
|
721
|
-
event_spec = call_event_handler(callback, _callback_arg_spec)
|
|
722
|
-
elif isinstance(callback, FunctionType):
|
|
723
|
-
event_spec = call_event_fn(callback, _callback_arg_spec)[0]
|
|
724
|
-
else:
|
|
725
|
-
raise ValueError("Cannot use {callback!r} as a call_script callback.")
|
|
726
734
|
callback_kwargs = {
|
|
727
|
-
"callback":
|
|
735
|
+
"callback": format.format_queue_events(
|
|
736
|
+
callback,
|
|
737
|
+
args_spec=lambda result: [result],
|
|
738
|
+
)
|
|
728
739
|
}
|
|
729
740
|
return server_side(
|
|
730
741
|
"_call_script",
|
|
@@ -834,10 +845,11 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
|
|
|
834
845
|
The event specs from calling the function.
|
|
835
846
|
|
|
836
847
|
Raises:
|
|
837
|
-
|
|
848
|
+
EventHandlerValueError: If the lambda has an invalid signature.
|
|
838
849
|
"""
|
|
839
850
|
# Import here to avoid circular imports.
|
|
840
851
|
from reflex.event import EventHandler, EventSpec
|
|
852
|
+
from reflex.utils.exceptions import EventHandlerValueError
|
|
841
853
|
|
|
842
854
|
# Get the args of the lambda.
|
|
843
855
|
args = inspect.getfullargspec(fn).args
|
|
@@ -851,7 +863,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
|
|
|
851
863
|
elif len(args) == 1:
|
|
852
864
|
out = fn(arg)
|
|
853
865
|
else:
|
|
854
|
-
raise
|
|
866
|
+
raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
|
|
855
867
|
|
|
856
868
|
# Convert the output to a list.
|
|
857
869
|
if not isinstance(out, List):
|
|
@@ -869,7 +881,9 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
|
|
|
869
881
|
|
|
870
882
|
# Make sure the event spec is valid.
|
|
871
883
|
if not isinstance(e, EventSpec):
|
|
872
|
-
raise
|
|
884
|
+
raise EventHandlerValueError(
|
|
885
|
+
f"Lambda {fn} returned an invalid event spec: {e}."
|
|
886
|
+
)
|
|
873
887
|
|
|
874
888
|
# Add the event spec to the chain.
|
|
875
889
|
events.append(e)
|
reflex/experimental/__init__.py
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from types import SimpleNamespace
|
|
4
4
|
|
|
5
|
+
from reflex.components.props import PropsBase
|
|
5
6
|
from reflex.components.radix.themes.components.progress import progress as progress
|
|
6
7
|
from reflex.components.sonner.toast import toast as toast
|
|
7
8
|
|
|
8
9
|
from ..utils.console import warn
|
|
9
10
|
from . import hooks as hooks
|
|
11
|
+
from .client_state import ClientStateVar as ClientStateVar
|
|
10
12
|
from .layout import layout as layout
|
|
11
13
|
from .misc import run_in_thread as run_in_thread
|
|
12
14
|
|
|
@@ -15,9 +17,11 @@ warn(
|
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
_x = SimpleNamespace(
|
|
20
|
+
client_state=ClientStateVar.create,
|
|
18
21
|
hooks=hooks,
|
|
19
22
|
layout=layout,
|
|
20
23
|
progress=progress,
|
|
24
|
+
PropsBase=PropsBase,
|
|
21
25
|
run_in_thread=run_in_thread,
|
|
22
26
|
toast=toast,
|
|
23
27
|
)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Handle client side state with `useState`."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Callable, Optional, Type
|
|
6
|
+
|
|
7
|
+
from reflex import constants
|
|
8
|
+
from reflex.event import EventChain, EventHandler, EventSpec, call_script
|
|
9
|
+
from reflex.utils.imports import ImportVar
|
|
10
|
+
from reflex.vars import Var, VarData
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _client_state_ref(var_name: str) -> str:
|
|
14
|
+
"""Get the ref path for a ClientStateVar.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
var_name: The name of the variable.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
An accessor for ClientStateVar ref as a string.
|
|
21
|
+
"""
|
|
22
|
+
return f"refs['_client_state_{var_name}']"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass(
|
|
26
|
+
eq=False,
|
|
27
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
28
|
+
)
|
|
29
|
+
class ClientStateVar(Var):
|
|
30
|
+
"""A Var that exists on the client via useState."""
|
|
31
|
+
|
|
32
|
+
# The name of the var.
|
|
33
|
+
_var_name: str = dataclasses.field()
|
|
34
|
+
|
|
35
|
+
# Track the names of the getters and setters
|
|
36
|
+
_setter_name: str = dataclasses.field()
|
|
37
|
+
_getter_name: str = dataclasses.field()
|
|
38
|
+
|
|
39
|
+
# The type of the var.
|
|
40
|
+
_var_type: Type = dataclasses.field(default=Any)
|
|
41
|
+
|
|
42
|
+
# Whether this is a local javascript variable.
|
|
43
|
+
_var_is_local: bool = dataclasses.field(default=False)
|
|
44
|
+
|
|
45
|
+
# Whether the var is a string literal.
|
|
46
|
+
_var_is_string: bool = dataclasses.field(default=False)
|
|
47
|
+
|
|
48
|
+
# _var_full_name should be prefixed with _var_state
|
|
49
|
+
_var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
|
|
50
|
+
|
|
51
|
+
# Extra metadata associated with the Var
|
|
52
|
+
_var_data: Optional[VarData] = dataclasses.field(default=None)
|
|
53
|
+
|
|
54
|
+
def __hash__(self) -> int:
|
|
55
|
+
"""Define a hash function for a var.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The hash of the var.
|
|
59
|
+
"""
|
|
60
|
+
return hash(
|
|
61
|
+
(self._var_name, str(self._var_type), self._getter_name, self._setter_name)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def create(cls, var_name, default=None) -> "ClientStateVar":
|
|
66
|
+
"""Create a local_state Var that can be accessed and updated on the client.
|
|
67
|
+
|
|
68
|
+
The `ClientStateVar` should be included in the highest parent component
|
|
69
|
+
that contains the components which will access and manipulate the client
|
|
70
|
+
state. It has no visual rendering, including it ensures that the
|
|
71
|
+
`useState` hook is called in the correct scope.
|
|
72
|
+
|
|
73
|
+
To render the var in a component, use the `value` property.
|
|
74
|
+
|
|
75
|
+
To update the var in a component, use the `set` property.
|
|
76
|
+
|
|
77
|
+
To access the var in an event handler, use the `retrieve` method with
|
|
78
|
+
`callback` set to the event handler which should receive the value.
|
|
79
|
+
|
|
80
|
+
To update the var in an event handler, use the `push` method with the
|
|
81
|
+
value to update.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
var_name: The name of the variable.
|
|
85
|
+
default: The default value of the variable.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ClientStateVar
|
|
89
|
+
"""
|
|
90
|
+
if default is None:
|
|
91
|
+
default_var = Var.create_safe("", _var_is_local=False, _var_is_string=False)
|
|
92
|
+
elif not isinstance(default, Var):
|
|
93
|
+
default_var = Var.create_safe(default)
|
|
94
|
+
else:
|
|
95
|
+
default_var = default
|
|
96
|
+
setter_name = f"set{var_name.capitalize()}"
|
|
97
|
+
return cls(
|
|
98
|
+
_var_name="",
|
|
99
|
+
_setter_name=setter_name,
|
|
100
|
+
_getter_name=var_name,
|
|
101
|
+
_var_is_local=False,
|
|
102
|
+
_var_is_string=False,
|
|
103
|
+
_var_type=default_var._var_type,
|
|
104
|
+
_var_data=VarData.merge(
|
|
105
|
+
default_var._var_data,
|
|
106
|
+
VarData( # type: ignore
|
|
107
|
+
hooks={
|
|
108
|
+
f"const [{var_name}, {setter_name}] = useState({default_var._var_name_unwrapped})": None,
|
|
109
|
+
f"{_client_state_ref(var_name)} = {var_name}": None,
|
|
110
|
+
f"{_client_state_ref(setter_name)} = {setter_name}": None,
|
|
111
|
+
},
|
|
112
|
+
imports={
|
|
113
|
+
"react": [ImportVar(tag="useState", install=False)],
|
|
114
|
+
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
|
|
115
|
+
},
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def value(self) -> Var:
|
|
122
|
+
"""Get a placeholder for the Var.
|
|
123
|
+
|
|
124
|
+
This property can only be rendered on the frontend.
|
|
125
|
+
|
|
126
|
+
To access the value in a backend event handler, see `retrieve`.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
an accessor for the client state variable.
|
|
130
|
+
"""
|
|
131
|
+
return (
|
|
132
|
+
Var.create_safe(
|
|
133
|
+
_client_state_ref(self._getter_name),
|
|
134
|
+
_var_is_local=False,
|
|
135
|
+
_var_is_string=False,
|
|
136
|
+
)
|
|
137
|
+
.to(self._var_type)
|
|
138
|
+
._replace(
|
|
139
|
+
merge_var_data=VarData( # type: ignore
|
|
140
|
+
imports={
|
|
141
|
+
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def set(self) -> Var:
|
|
149
|
+
"""Set the value of the client state variable.
|
|
150
|
+
|
|
151
|
+
This property can only be attached to a frontend event trigger.
|
|
152
|
+
|
|
153
|
+
To set a value from a backend event handler, see `push`.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A special EventChain Var which will set the value when triggered.
|
|
157
|
+
"""
|
|
158
|
+
return (
|
|
159
|
+
Var.create_safe(
|
|
160
|
+
_client_state_ref(self._setter_name),
|
|
161
|
+
_var_is_local=False,
|
|
162
|
+
_var_is_string=False,
|
|
163
|
+
)
|
|
164
|
+
.to(EventChain)
|
|
165
|
+
._replace(
|
|
166
|
+
merge_var_data=VarData( # type: ignore
|
|
167
|
+
imports={
|
|
168
|
+
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def retrieve(self, callback: EventHandler | Callable | None = None) -> EventSpec:
|
|
175
|
+
"""Pass the value of the client state variable to a backend EventHandler.
|
|
176
|
+
|
|
177
|
+
The event handler must `yield` or `return` the EventSpec to trigger the event.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
callback: The callback to pass the value to.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
An EventSpec which will retrieve the value when triggered.
|
|
184
|
+
"""
|
|
185
|
+
return call_script(_client_state_ref(self._getter_name), callback=callback)
|
|
186
|
+
|
|
187
|
+
def push(self, value: Any) -> EventSpec:
|
|
188
|
+
"""Push a value to the client state variable from the backend.
|
|
189
|
+
|
|
190
|
+
The event handler must `yield` or `return` the EventSpec to trigger the event.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
value: The value to update.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
An EventSpec which will push the value when triggered.
|
|
197
|
+
"""
|
|
198
|
+
return call_script(f"{_client_state_ref(self._setter_name)}({value})")
|
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,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1454
1475
|
Yields:
|
|
1455
1476
|
StateUpdate object
|
|
1456
1477
|
"""
|
|
1478
|
+
from reflex.utils import telemetry
|
|
1479
|
+
from reflex.utils.exceptions import ReflexError
|
|
1480
|
+
|
|
1457
1481
|
# Get the function to process the event.
|
|
1458
1482
|
fn = functools.partial(handler.fn, state)
|
|
1459
1483
|
|
|
@@ -1489,9 +1513,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1489
1513
|
yield state._as_state_update(handler, events, final=True)
|
|
1490
1514
|
|
|
1491
1515
|
# If an error occurs, throw a window alert.
|
|
1492
|
-
except Exception:
|
|
1516
|
+
except Exception as ex:
|
|
1493
1517
|
error = traceback.format_exc()
|
|
1494
1518
|
print(error)
|
|
1519
|
+
if isinstance(ex, ReflexError):
|
|
1520
|
+
telemetry.send("error", context="backend", detail=str(ex))
|
|
1495
1521
|
yield state._as_state_update(
|
|
1496
1522
|
handler,
|
|
1497
1523
|
window_alert("An error occurred. See logs for details."),
|
|
@@ -1688,10 +1714,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1688
1714
|
if initial:
|
|
1689
1715
|
computed_vars = {
|
|
1690
1716
|
# Include initial computed vars.
|
|
1691
|
-
prop_name:
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1717
|
+
prop_name: (
|
|
1718
|
+
cv._initial_value
|
|
1719
|
+
if isinstance(cv, ComputedVar)
|
|
1720
|
+
and not isinstance(cv._initial_value, types.Unset)
|
|
1721
|
+
else self.get_value(getattr(self, prop_name))
|
|
1722
|
+
)
|
|
1695
1723
|
for prop_name, cv in self.computed_vars.items()
|
|
1696
1724
|
}
|
|
1697
1725
|
elif include_computed:
|
|
@@ -1818,7 +1846,7 @@ class OnLoadInternalState(State):
|
|
|
1818
1846
|
]
|
|
1819
1847
|
|
|
1820
1848
|
|
|
1821
|
-
class ComponentState(
|
|
1849
|
+
class ComponentState(State, mixin=True):
|
|
1822
1850
|
"""Base class to allow for the creation of a state instance per component.
|
|
1823
1851
|
|
|
1824
1852
|
This allows for the bundling of UI and state logic into a single class,
|
|
@@ -1860,6 +1888,18 @@ class ComponentState(Base):
|
|
|
1860
1888
|
# The number of components created from this class.
|
|
1861
1889
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
1862
1890
|
|
|
1891
|
+
@classmethod
|
|
1892
|
+
def __init_subclass__(cls, mixin: bool = False, **kwargs):
|
|
1893
|
+
"""Overwrite mixin default to True.
|
|
1894
|
+
|
|
1895
|
+
Args:
|
|
1896
|
+
mixin: Whether the subclass is a mixin and should not be initialized.
|
|
1897
|
+
**kwargs: The kwargs to pass to the pydantic init_subclass method.
|
|
1898
|
+
"""
|
|
1899
|
+
if ComponentState in cls.__bases__:
|
|
1900
|
+
mixin = True
|
|
1901
|
+
super().__init_subclass__(mixin=mixin, **kwargs)
|
|
1902
|
+
|
|
1863
1903
|
@classmethod
|
|
1864
1904
|
def get_component(cls, *children, **props) -> "Component":
|
|
1865
1905
|
"""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,
|