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/event.py
CHANGED
|
@@ -18,10 +18,12 @@ from typing import (
|
|
|
18
18
|
get_type_hints,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
from typing_extensions import get_args, get_origin
|
|
22
|
+
|
|
21
23
|
from reflex import constants
|
|
22
24
|
from reflex.utils import format
|
|
23
25
|
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
|
|
24
|
-
from reflex.utils.types import ArgsSpec
|
|
26
|
+
from reflex.utils.types import ArgsSpec, GenericType
|
|
25
27
|
from reflex.vars import VarData
|
|
26
28
|
from reflex.vars.base import LiteralVar, Var
|
|
27
29
|
from reflex.vars.function import FunctionStringVar, FunctionVar
|
|
@@ -217,7 +219,7 @@ class EventHandler(EventActionsMixin):
|
|
|
217
219
|
raise EventHandlerTypeError(
|
|
218
220
|
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
|
|
219
221
|
) from e
|
|
220
|
-
payload = tuple(zip(fn_args, values
|
|
222
|
+
payload = tuple(zip(fn_args, values))
|
|
221
223
|
|
|
222
224
|
# Return the event spec.
|
|
223
225
|
return EventSpec(
|
|
@@ -310,7 +312,7 @@ class EventSpec(EventActionsMixin):
|
|
|
310
312
|
raise EventHandlerTypeError(
|
|
311
313
|
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
|
|
312
314
|
) from e
|
|
313
|
-
new_payload = tuple(zip(fn_args, values
|
|
315
|
+
new_payload = tuple(zip(fn_args, values))
|
|
314
316
|
return self.with_args(self.args + new_payload)
|
|
315
317
|
|
|
316
318
|
|
|
@@ -417,7 +419,7 @@ class FileUpload:
|
|
|
417
419
|
on_upload_progress: Optional[Union[EventHandler, Callable]] = None
|
|
418
420
|
|
|
419
421
|
@staticmethod
|
|
420
|
-
def on_upload_progress_args_spec(_prog: Dict[str, Union[int, float, bool]]):
|
|
422
|
+
def on_upload_progress_args_spec(_prog: Var[Dict[str, Union[int, float, bool]]]):
|
|
421
423
|
"""Args spec for on_upload_progress event handler.
|
|
422
424
|
|
|
423
425
|
Returns:
|
|
@@ -910,6 +912,20 @@ def call_event_handler(
|
|
|
910
912
|
)
|
|
911
913
|
|
|
912
914
|
|
|
915
|
+
def unwrap_var_annotation(annotation: GenericType):
|
|
916
|
+
"""Unwrap a Var annotation or return it as is if it's not Var[X].
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
annotation: The annotation to unwrap.
|
|
920
|
+
|
|
921
|
+
Returns:
|
|
922
|
+
The unwrapped annotation.
|
|
923
|
+
"""
|
|
924
|
+
if get_origin(annotation) is Var and (args := get_args(annotation)):
|
|
925
|
+
return args[0]
|
|
926
|
+
return annotation
|
|
927
|
+
|
|
928
|
+
|
|
913
929
|
def parse_args_spec(arg_spec: ArgsSpec):
|
|
914
930
|
"""Parse the args provided in the ArgsSpec of an event trigger.
|
|
915
931
|
|
|
@@ -921,20 +937,54 @@ def parse_args_spec(arg_spec: ArgsSpec):
|
|
|
921
937
|
"""
|
|
922
938
|
spec = inspect.getfullargspec(arg_spec)
|
|
923
939
|
annotations = get_type_hints(arg_spec)
|
|
940
|
+
|
|
924
941
|
return arg_spec(
|
|
925
942
|
*[
|
|
926
|
-
Var(f"_{l_arg}").to(
|
|
943
|
+
Var(f"_{l_arg}").to(
|
|
944
|
+
unwrap_var_annotation(annotations.get(l_arg, FrontendEvent))
|
|
945
|
+
)
|
|
927
946
|
for l_arg in spec.args
|
|
928
947
|
]
|
|
929
948
|
)
|
|
930
949
|
|
|
931
950
|
|
|
951
|
+
def check_fn_match_arg_spec(fn: Callable, arg_spec: ArgsSpec) -> List[Var]:
|
|
952
|
+
"""Ensures that the function signature matches the passed argument specification
|
|
953
|
+
or raises an EventFnArgMismatch if they do not.
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
fn: The function to be validated.
|
|
957
|
+
arg_spec: The argument specification for the event trigger.
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
The parsed arguments from the argument specification.
|
|
961
|
+
|
|
962
|
+
Raises:
|
|
963
|
+
EventFnArgMismatch: Raised if the number of mandatory arguments do not match
|
|
964
|
+
"""
|
|
965
|
+
fn_args = inspect.getfullargspec(fn).args
|
|
966
|
+
fn_defaults_args = inspect.getfullargspec(fn).defaults
|
|
967
|
+
n_fn_args = len(fn_args)
|
|
968
|
+
n_fn_defaults_args = len(fn_defaults_args) if fn_defaults_args else 0
|
|
969
|
+
if isinstance(fn, types.MethodType):
|
|
970
|
+
n_fn_args -= 1 # subtract 1 for bound self arg
|
|
971
|
+
parsed_args = parse_args_spec(arg_spec)
|
|
972
|
+
if not (n_fn_args - n_fn_defaults_args <= len(parsed_args) <= n_fn_args):
|
|
973
|
+
raise EventFnArgMismatch(
|
|
974
|
+
"The number of mandatory arguments accepted by "
|
|
975
|
+
f"{fn} ({n_fn_args - n_fn_defaults_args}) "
|
|
976
|
+
"does not match the arguments passed by the event trigger: "
|
|
977
|
+
f"{[str(v) for v in parsed_args]}\n"
|
|
978
|
+
"See https://reflex.dev/docs/events/event-arguments/"
|
|
979
|
+
)
|
|
980
|
+
return parsed_args
|
|
981
|
+
|
|
982
|
+
|
|
932
983
|
def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
|
|
933
984
|
"""Call a function to a list of event specs.
|
|
934
985
|
|
|
935
986
|
The function should return a single EventSpec, a list of EventSpecs, or a
|
|
936
|
-
single Var.
|
|
937
|
-
EventFnArgsMismatch will be raised.
|
|
987
|
+
single Var.
|
|
938
988
|
|
|
939
989
|
Args:
|
|
940
990
|
fn: The function to call.
|
|
@@ -944,7 +994,6 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
|
|
|
944
994
|
The event specs from calling the function or a Var.
|
|
945
995
|
|
|
946
996
|
Raises:
|
|
947
|
-
EventFnArgMismatch: If the function signature doesn't match the arg spec.
|
|
948
997
|
EventHandlerValueError: If the lambda returns an unusable value.
|
|
949
998
|
"""
|
|
950
999
|
# Import here to avoid circular imports.
|
|
@@ -952,19 +1001,7 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
|
|
|
952
1001
|
from reflex.utils.exceptions import EventHandlerValueError
|
|
953
1002
|
|
|
954
1003
|
# Check that fn signature matches arg_spec
|
|
955
|
-
|
|
956
|
-
n_fn_args = len(fn_args)
|
|
957
|
-
if isinstance(fn, types.MethodType):
|
|
958
|
-
n_fn_args -= 1 # subtract 1 for bound self arg
|
|
959
|
-
parsed_args = parse_args_spec(arg_spec)
|
|
960
|
-
if len(parsed_args) != n_fn_args:
|
|
961
|
-
raise EventFnArgMismatch(
|
|
962
|
-
"The number of arguments accepted by "
|
|
963
|
-
f"{fn} ({n_fn_args}) "
|
|
964
|
-
"does not match the arguments passed by the event trigger: "
|
|
965
|
-
f"{[str(v) for v in parsed_args]}\n"
|
|
966
|
-
"See https://reflex.dev/docs/events/event-arguments/"
|
|
967
|
-
)
|
|
1004
|
+
parsed_args = check_fn_match_arg_spec(fn, arg_spec)
|
|
968
1005
|
|
|
969
1006
|
# Call the function with the parsed args.
|
|
970
1007
|
out = fn(*parsed_args)
|
|
@@ -1025,6 +1062,9 @@ def fix_events(
|
|
|
1025
1062
|
token: The user token.
|
|
1026
1063
|
router_data: The optional router data to set in the event.
|
|
1027
1064
|
|
|
1065
|
+
Raises:
|
|
1066
|
+
ValueError: If the event type is not what was expected.
|
|
1067
|
+
|
|
1028
1068
|
Returns:
|
|
1029
1069
|
The fixed events.
|
|
1030
1070
|
"""
|
|
@@ -1048,7 +1088,8 @@ def fix_events(
|
|
|
1048
1088
|
# Otherwise, create an event from the event spec.
|
|
1049
1089
|
if isinstance(e, EventHandler):
|
|
1050
1090
|
e = e()
|
|
1051
|
-
|
|
1091
|
+
if not isinstance(e, EventSpec):
|
|
1092
|
+
raise ValueError(f"Unexpected event type, {type(e)}.")
|
|
1052
1093
|
name = format.format_event_handler(e.handler)
|
|
1053
1094
|
payload = {k._js_expr: v._decode() for k, v in e.args} # type: ignore
|
|
1054
1095
|
|
reflex/experimental/assets.py
CHANGED
|
@@ -24,6 +24,7 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
|
|
|
24
24
|
|
|
25
25
|
Raises:
|
|
26
26
|
FileNotFoundError: If the file does not exist.
|
|
27
|
+
ValueError: If the module is None.
|
|
27
28
|
|
|
28
29
|
Returns:
|
|
29
30
|
The relative URL to the copied asset.
|
|
@@ -31,7 +32,8 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
|
|
|
31
32
|
# Determine the file by which the asset is exposed.
|
|
32
33
|
calling_file = inspect.stack()[1].filename
|
|
33
34
|
module = inspect.getmodule(inspect.stack()[1][0])
|
|
34
|
-
|
|
35
|
+
if module is None:
|
|
36
|
+
raise ValueError("Module is None")
|
|
35
37
|
caller_module_path = module.__name__.replace(".", "/")
|
|
36
38
|
|
|
37
39
|
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import dataclasses
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
from typing import Any, Callable, Union
|
|
8
9
|
|
|
@@ -90,12 +91,16 @@ class ClientStateVar(Var):
|
|
|
90
91
|
default: The default value of the variable.
|
|
91
92
|
global_ref: Whether the state should be accessible in any Component and on the backend.
|
|
92
93
|
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If the var_name is not a string.
|
|
96
|
+
|
|
93
97
|
Returns:
|
|
94
98
|
ClientStateVar
|
|
95
99
|
"""
|
|
96
100
|
if var_name is None:
|
|
97
101
|
var_name = get_unique_variable_name()
|
|
98
|
-
|
|
102
|
+
if not isinstance(var_name, str):
|
|
103
|
+
raise ValueError("var_name must be a string.")
|
|
99
104
|
if default is NoValue:
|
|
100
105
|
default_var = Var(_js_expr="")
|
|
101
106
|
elif not isinstance(default, Var):
|
|
@@ -174,15 +179,15 @@ class ClientStateVar(Var):
|
|
|
174
179
|
else self._setter_name
|
|
175
180
|
)
|
|
176
181
|
if value is not NoValue:
|
|
177
|
-
import re
|
|
178
|
-
|
|
179
182
|
# This is a hack to make it work like an EventSpec taking an arg
|
|
180
183
|
value_str = str(LiteralVar.create(value))
|
|
181
184
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
if value_str.startswith("_"):
|
|
186
|
+
# remove patterns of ["*"] from the value_str using regex
|
|
187
|
+
arg = re.sub(r"\[\".*\"\]", "", value_str)
|
|
188
|
+
setter = f"(({arg}) => {setter}({value_str}))"
|
|
189
|
+
else:
|
|
190
|
+
setter = f"(() => {setter}({value_str}))"
|
|
186
191
|
return Var(
|
|
187
192
|
_js_expr=setter,
|
|
188
193
|
_var_data=VarData(imports=_refs_import if self._global_ref else {}),
|
reflex/experimental/misc.py
CHANGED
|
@@ -12,10 +12,12 @@ async def run_in_thread(func) -> Any:
|
|
|
12
12
|
Args:
|
|
13
13
|
func (callable): The non-async function to run.
|
|
14
14
|
|
|
15
|
+
Raises:
|
|
16
|
+
ValueError: If the function is an async function.
|
|
17
|
+
|
|
15
18
|
Returns:
|
|
16
19
|
Any: The return value of the function.
|
|
17
20
|
"""
|
|
18
|
-
|
|
19
|
-
func
|
|
20
|
-
), "func must be a non-async function"
|
|
21
|
+
if asyncio.coroutines.iscoroutinefunction(func):
|
|
22
|
+
raise ValueError("func must be a non-async function")
|
|
21
23
|
return await asyncio.get_event_loop().run_in_executor(None, func)
|
|
@@ -9,7 +9,6 @@ from reflex import constants
|
|
|
9
9
|
from reflex.event import Event, get_hydrate_event
|
|
10
10
|
from reflex.middleware.middleware import Middleware
|
|
11
11
|
from reflex.state import BaseState, StateUpdate
|
|
12
|
-
from reflex.utils import format
|
|
13
12
|
|
|
14
13
|
if TYPE_CHECKING:
|
|
15
14
|
from reflex.app import App
|
|
@@ -43,7 +42,7 @@ class HydrateMiddleware(Middleware):
|
|
|
43
42
|
setattr(state, constants.CompileVars.IS_HYDRATED, False)
|
|
44
43
|
|
|
45
44
|
# Get the initial state.
|
|
46
|
-
delta =
|
|
45
|
+
delta = state.dict()
|
|
47
46
|
# since a full dict was captured, clean any dirtiness
|
|
48
47
|
state._clean()
|
|
49
48
|
|
reflex/page.py
CHANGED
|
@@ -65,13 +65,20 @@ def page(
|
|
|
65
65
|
return decorator
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def get_decorated_pages() -> list[dict]:
|
|
68
|
+
def get_decorated_pages(omit_implicit_routes=True) -> list[dict[str, Any]]:
|
|
69
69
|
"""Get the decorated pages.
|
|
70
70
|
|
|
71
|
+
Args:
|
|
72
|
+
omit_implicit_routes: Whether to omit pages where the route will be implicitely guessed later.
|
|
73
|
+
|
|
71
74
|
Returns:
|
|
72
75
|
The decorated pages.
|
|
73
76
|
"""
|
|
74
77
|
return sorted(
|
|
75
|
-
[
|
|
76
|
-
|
|
78
|
+
[
|
|
79
|
+
page_data
|
|
80
|
+
for _, page_data in DECORATED_PAGES[get_config().app_name]
|
|
81
|
+
if not omit_implicit_routes or "route" in page_data
|
|
82
|
+
],
|
|
83
|
+
key=lambda x: x.get("route", ""),
|
|
77
84
|
)
|
reflex/reflex.py
CHANGED
|
@@ -15,6 +15,7 @@ from reflex_cli.utils import dependency
|
|
|
15
15
|
|
|
16
16
|
from reflex import constants
|
|
17
17
|
from reflex.config import get_config
|
|
18
|
+
from reflex.constants.base import LogLevel
|
|
18
19
|
from reflex.custom_components.custom_components import custom_components_cli
|
|
19
20
|
from reflex.state import reset_disk_state_manager
|
|
20
21
|
from reflex.utils import console, redir, telemetry
|
|
@@ -229,7 +230,8 @@ def _run(
|
|
|
229
230
|
exec.run_frontend_prod,
|
|
230
231
|
exec.run_backend_prod,
|
|
231
232
|
)
|
|
232
|
-
|
|
233
|
+
if not setup_frontend or not frontend_cmd or not backend_cmd:
|
|
234
|
+
raise ValueError("Invalid env")
|
|
233
235
|
|
|
234
236
|
# Post a telemetry event.
|
|
235
237
|
telemetry.send(f"run-{env.value}")
|
|
@@ -245,15 +247,30 @@ def _run(
|
|
|
245
247
|
setup_frontend(Path.cwd())
|
|
246
248
|
commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
|
|
247
249
|
|
|
250
|
+
# If no loglevel is specified, set the subprocesses loglevel to WARNING.
|
|
251
|
+
subprocesses_loglevel = (
|
|
252
|
+
loglevel if loglevel != LogLevel.DEFAULT else LogLevel.WARNING
|
|
253
|
+
)
|
|
254
|
+
|
|
248
255
|
# In prod mode, run the backend on a separate thread.
|
|
249
256
|
if backend and env == constants.Env.PROD:
|
|
250
|
-
commands.append(
|
|
257
|
+
commands.append(
|
|
258
|
+
(
|
|
259
|
+
backend_cmd,
|
|
260
|
+
backend_host,
|
|
261
|
+
backend_port,
|
|
262
|
+
subprocesses_loglevel,
|
|
263
|
+
frontend,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
251
266
|
|
|
252
267
|
# Start the frontend and backend.
|
|
253
268
|
with processes.run_concurrently_context(*commands):
|
|
254
269
|
# In dev mode, run the backend on the main thread.
|
|
255
270
|
if backend and env == constants.Env.DEV:
|
|
256
|
-
backend_cmd(
|
|
271
|
+
backend_cmd(
|
|
272
|
+
backend_host, int(backend_port), subprocesses_loglevel, frontend
|
|
273
|
+
)
|
|
257
274
|
# The windows uvicorn bug workaround
|
|
258
275
|
# https://github.com/reflex-dev/reflex/issues/2335
|
|
259
276
|
if constants.IS_WINDOWS and exec.frontend_process:
|
reflex/state.py
CHANGED
|
@@ -12,6 +12,7 @@ import os
|
|
|
12
12
|
import uuid
|
|
13
13
|
from abc import ABC, abstractmethod
|
|
14
14
|
from collections import defaultdict
|
|
15
|
+
from hashlib import md5
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from types import FunctionType, MethodType
|
|
17
18
|
from typing import (
|
|
@@ -29,10 +30,12 @@ from typing import (
|
|
|
29
30
|
Type,
|
|
30
31
|
Union,
|
|
31
32
|
cast,
|
|
33
|
+
get_type_hints,
|
|
32
34
|
)
|
|
33
35
|
|
|
34
36
|
import dill
|
|
35
37
|
from sqlalchemy.orm import DeclarativeBase
|
|
38
|
+
from typing_extensions import Self
|
|
36
39
|
|
|
37
40
|
from reflex.config import get_config
|
|
38
41
|
from reflex.vars.base import (
|
|
@@ -40,6 +43,8 @@ from reflex.vars.base import (
|
|
|
40
43
|
DynamicRouteVar,
|
|
41
44
|
Var,
|
|
42
45
|
computed_var,
|
|
46
|
+
dispatch,
|
|
47
|
+
get_unique_variable_name,
|
|
43
48
|
is_computed_var,
|
|
44
49
|
)
|
|
45
50
|
|
|
@@ -71,7 +76,7 @@ from reflex.utils.exceptions import (
|
|
|
71
76
|
LockExpiredError,
|
|
72
77
|
)
|
|
73
78
|
from reflex.utils.exec import is_testing_env
|
|
74
|
-
from reflex.utils.serializers import
|
|
79
|
+
from reflex.utils.serializers import serializer
|
|
75
80
|
from reflex.utils.types import override
|
|
76
81
|
from reflex.vars import VarData
|
|
77
82
|
|
|
@@ -336,6 +341,29 @@ class EventHandlerSetVar(EventHandler):
|
|
|
336
341
|
return super().__call__(*args)
|
|
337
342
|
|
|
338
343
|
|
|
344
|
+
if TYPE_CHECKING:
|
|
345
|
+
from pydantic.v1.fields import ModelField
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def get_var_for_field(cls: Type[BaseState], f: ModelField):
|
|
349
|
+
"""Get a Var instance for a Pydantic field.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
cls: The state class.
|
|
353
|
+
f: The Pydantic field.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
The Var instance.
|
|
357
|
+
"""
|
|
358
|
+
field_name = format.format_state_name(cls.get_full_name()) + "." + f.name
|
|
359
|
+
|
|
360
|
+
return dispatch(
|
|
361
|
+
field_name=field_name,
|
|
362
|
+
var_data=VarData.from_state(cls, f.name),
|
|
363
|
+
result_var_type=f.outer_type_,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
339
367
|
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
340
368
|
"""The state of the app."""
|
|
341
369
|
|
|
@@ -548,6 +576,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
548
576
|
for name, value in cls.__dict__.items()
|
|
549
577
|
if types.is_backend_base_variable(name, cls)
|
|
550
578
|
}
|
|
579
|
+
# Add annotated backend vars that do not have a default value.
|
|
580
|
+
new_backend_vars.update(
|
|
581
|
+
{
|
|
582
|
+
name: Var("", _var_type=annotation_value).get_default_value()
|
|
583
|
+
for name, annotation_value in get_type_hints(cls).items()
|
|
584
|
+
if name not in new_backend_vars
|
|
585
|
+
and types.is_backend_base_variable(name, cls)
|
|
586
|
+
}
|
|
587
|
+
)
|
|
551
588
|
|
|
552
589
|
cls.backend_vars = {
|
|
553
590
|
**cls.inherited_backend_vars,
|
|
@@ -556,11 +593,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
556
593
|
|
|
557
594
|
# Set the base and computed vars.
|
|
558
595
|
cls.base_vars = {
|
|
559
|
-
f.name:
|
|
560
|
-
_js_expr=format.format_state_name(cls.get_full_name()) + "." + f.name,
|
|
561
|
-
_var_type=f.outer_type_,
|
|
562
|
-
_var_data=VarData.from_state(cls),
|
|
563
|
-
).guess_type()
|
|
596
|
+
f.name: get_var_for_field(cls, f)
|
|
564
597
|
for f in cls.get_fields().values()
|
|
565
598
|
if f.name not in cls.get_skip_vars()
|
|
566
599
|
}
|
|
@@ -664,6 +697,36 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
664
697
|
and hasattr(value, "__code__")
|
|
665
698
|
)
|
|
666
699
|
|
|
700
|
+
@classmethod
|
|
701
|
+
def _evaluate(cls, f: Callable[[Self], Any]) -> Var:
|
|
702
|
+
"""Evaluate a function to a ComputedVar. Experimental.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
f: The function to evaluate.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
The ComputedVar.
|
|
709
|
+
"""
|
|
710
|
+
console.warn(
|
|
711
|
+
"The _evaluate method is experimental and may be removed in future versions."
|
|
712
|
+
)
|
|
713
|
+
from reflex.components.base.fragment import fragment
|
|
714
|
+
from reflex.components.component import Component
|
|
715
|
+
|
|
716
|
+
unique_var_name = get_unique_variable_name()
|
|
717
|
+
|
|
718
|
+
@computed_var(_js_expr=unique_var_name, return_type=Component)
|
|
719
|
+
def computed_var_func(state: Self):
|
|
720
|
+
return fragment(f(state))
|
|
721
|
+
|
|
722
|
+
setattr(cls, unique_var_name, computed_var_func)
|
|
723
|
+
cls.computed_vars[unique_var_name] = computed_var_func
|
|
724
|
+
cls.vars[unique_var_name] = computed_var_func
|
|
725
|
+
cls._update_substate_inherited_vars({unique_var_name: computed_var_func})
|
|
726
|
+
cls._always_dirty_computed_vars.add(unique_var_name)
|
|
727
|
+
|
|
728
|
+
return getattr(cls, unique_var_name)
|
|
729
|
+
|
|
667
730
|
@classmethod
|
|
668
731
|
def _mixins(cls) -> List[Type]:
|
|
669
732
|
"""Get the mixin classes of the state.
|
|
@@ -807,6 +870,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
807
870
|
def get_parent_state(cls) -> Type[BaseState] | None:
|
|
808
871
|
"""Get the parent state.
|
|
809
872
|
|
|
873
|
+
Raises:
|
|
874
|
+
ValueError: If more than one parent state is found.
|
|
875
|
+
|
|
810
876
|
Returns:
|
|
811
877
|
The parent state.
|
|
812
878
|
"""
|
|
@@ -815,9 +881,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
815
881
|
for base in cls.__bases__
|
|
816
882
|
if issubclass(base, BaseState) and base is not BaseState and not base._mixin
|
|
817
883
|
]
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
), f"Only one parent state is allowed {parent_states}."
|
|
884
|
+
if len(parent_states) >= 2:
|
|
885
|
+
raise ValueError(f"Only one parent state is allowed {parent_states}.")
|
|
821
886
|
return parent_states[0] if len(parent_states) == 1 else None # type: ignore
|
|
822
887
|
|
|
823
888
|
@classmethod
|
|
@@ -948,7 +1013,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
948
1013
|
var = Var(
|
|
949
1014
|
_js_expr=format.format_state_name(cls.get_full_name()) + "." + name,
|
|
950
1015
|
_var_type=type_,
|
|
951
|
-
_var_data=VarData.from_state(cls),
|
|
1016
|
+
_var_data=VarData.from_state(cls, name),
|
|
952
1017
|
).guess_type()
|
|
953
1018
|
|
|
954
1019
|
# add the pydantic field dynamically (must be done before _init_var)
|
|
@@ -974,10 +1039,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
974
1039
|
Args:
|
|
975
1040
|
prop: The var instance to set.
|
|
976
1041
|
"""
|
|
977
|
-
|
|
978
|
-
prop._js_expr if "." not in prop._js_expr else prop._js_expr.split(".")[-1]
|
|
979
|
-
)
|
|
980
|
-
setattr(cls, acutal_var_name, prop)
|
|
1042
|
+
setattr(cls, prop._var_field_name, prop)
|
|
981
1043
|
|
|
982
1044
|
@classmethod
|
|
983
1045
|
def _create_event_handler(cls, fn):
|
|
@@ -1017,10 +1079,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1017
1079
|
prop: The var to set the default value for.
|
|
1018
1080
|
"""
|
|
1019
1081
|
# Get the pydantic field for the var.
|
|
1020
|
-
|
|
1021
|
-
field = cls.get_fields()[prop._js_expr.split(".")[-1]]
|
|
1022
|
-
else:
|
|
1023
|
-
field = cls.get_fields()[prop._js_expr]
|
|
1082
|
+
field = cls.get_fields()[prop._var_field_name]
|
|
1024
1083
|
if field.required:
|
|
1025
1084
|
default_value = prop.get_default_value()
|
|
1026
1085
|
if default_value is not None:
|
|
@@ -1302,7 +1361,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1302
1361
|
for part1, part2 in zip(
|
|
1303
1362
|
cls.get_full_name().split("."),
|
|
1304
1363
|
other.get_full_name().split("."),
|
|
1305
|
-
strict=False,
|
|
1306
1364
|
):
|
|
1307
1365
|
if part1 != part2:
|
|
1308
1366
|
break
|
|
@@ -1762,11 +1820,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1762
1820
|
.union(self._always_dirty_computed_vars)
|
|
1763
1821
|
)
|
|
1764
1822
|
|
|
1765
|
-
subdelta = {
|
|
1766
|
-
prop: getattr(self, prop)
|
|
1823
|
+
subdelta: Dict[str, Any] = {
|
|
1824
|
+
prop: self.get_value(getattr(self, prop))
|
|
1767
1825
|
for prop in delta_vars
|
|
1768
1826
|
if not types.is_backend_base_variable(prop, type(self))
|
|
1769
1827
|
}
|
|
1828
|
+
|
|
1770
1829
|
if len(subdelta) > 0:
|
|
1771
1830
|
delta[self.get_full_name()] = subdelta
|
|
1772
1831
|
|
|
@@ -1775,9 +1834,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1775
1834
|
for substate in self.dirty_substates.union(self._always_dirty_substates):
|
|
1776
1835
|
delta.update(substates[substate].get_delta())
|
|
1777
1836
|
|
|
1778
|
-
# Format the delta.
|
|
1779
|
-
delta = format.format_state(delta)
|
|
1780
|
-
|
|
1781
1837
|
# Return the delta.
|
|
1782
1838
|
return delta
|
|
1783
1839
|
|
|
@@ -2418,7 +2474,7 @@ class StateUpdate:
|
|
|
2418
2474
|
Returns:
|
|
2419
2475
|
The state update as a JSON string.
|
|
2420
2476
|
"""
|
|
2421
|
-
return format.json_dumps(
|
|
2477
|
+
return format.json_dumps(self)
|
|
2422
2478
|
|
|
2423
2479
|
|
|
2424
2480
|
class StateManager(Base, ABC):
|
|
@@ -2577,16 +2633,24 @@ def _serialize_type(type_: Any) -> str:
|
|
|
2577
2633
|
return f"{type_.__module__}.{type_.__qualname__}"
|
|
2578
2634
|
|
|
2579
2635
|
|
|
2636
|
+
def is_serializable(value: Any) -> bool:
|
|
2637
|
+
"""Check if a value is serializable.
|
|
2638
|
+
|
|
2639
|
+
Args:
|
|
2640
|
+
value: The value to check.
|
|
2641
|
+
|
|
2642
|
+
Returns:
|
|
2643
|
+
Whether the value is serializable.
|
|
2644
|
+
"""
|
|
2645
|
+
try:
|
|
2646
|
+
return bool(dill.dumps(value))
|
|
2647
|
+
except Exception:
|
|
2648
|
+
return False
|
|
2649
|
+
|
|
2650
|
+
|
|
2580
2651
|
def state_to_schema(
|
|
2581
2652
|
state: BaseState,
|
|
2582
|
-
) -> List[
|
|
2583
|
-
Tuple[
|
|
2584
|
-
str,
|
|
2585
|
-
str,
|
|
2586
|
-
Any,
|
|
2587
|
-
Union[bool, None],
|
|
2588
|
-
]
|
|
2589
|
-
]:
|
|
2653
|
+
) -> List[Tuple[str, str, Any, Union[bool, None], Any]]:
|
|
2590
2654
|
"""Convert a state to a schema.
|
|
2591
2655
|
|
|
2592
2656
|
Args:
|
|
@@ -2606,6 +2670,7 @@ def state_to_schema(
|
|
|
2606
2670
|
if isinstance(model_field.required, bool)
|
|
2607
2671
|
else None
|
|
2608
2672
|
),
|
|
2673
|
+
(model_field.default if is_serializable(model_field.default) else None),
|
|
2609
2674
|
)
|
|
2610
2675
|
for field_name, model_field in state.__fields__.items()
|
|
2611
2676
|
)
|
|
@@ -2690,7 +2755,9 @@ class StateManagerDisk(StateManager):
|
|
|
2690
2755
|
Returns:
|
|
2691
2756
|
The path for the token.
|
|
2692
2757
|
"""
|
|
2693
|
-
return (
|
|
2758
|
+
return (
|
|
2759
|
+
self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
|
|
2760
|
+
).absolute()
|
|
2694
2761
|
|
|
2695
2762
|
async def load_state(self, token: str, root_state: BaseState) -> BaseState:
|
|
2696
2763
|
"""Load a state object based on the provided token.
|
|
@@ -3634,22 +3701,16 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3634
3701
|
|
|
3635
3702
|
|
|
3636
3703
|
@serializer
|
|
3637
|
-
def serialize_mutable_proxy(mp: MutableProxy)
|
|
3638
|
-
"""
|
|
3704
|
+
def serialize_mutable_proxy(mp: MutableProxy):
|
|
3705
|
+
"""Return the wrapped value of a MutableProxy.
|
|
3639
3706
|
|
|
3640
3707
|
Args:
|
|
3641
3708
|
mp: The MutableProxy to serialize.
|
|
3642
3709
|
|
|
3643
3710
|
Returns:
|
|
3644
|
-
The
|
|
3645
|
-
|
|
3646
|
-
Raises:
|
|
3647
|
-
ValueError: when the wrapped object is not serializable.
|
|
3711
|
+
The wrapped object.
|
|
3648
3712
|
"""
|
|
3649
|
-
|
|
3650
|
-
if value is None:
|
|
3651
|
-
raise ValueError(f"Cannot serialize {type(mp.__wrapped__)}")
|
|
3652
|
-
return value
|
|
3713
|
+
return mp.__wrapped__
|
|
3653
3714
|
|
|
3654
3715
|
|
|
3655
3716
|
class ImmutableMutableProxy(MutableProxy):
|
reflex/style.py
CHANGED
|
@@ -13,6 +13,7 @@ from reflex.utils.imports import ImportVar
|
|
|
13
13
|
from reflex.vars import VarData
|
|
14
14
|
from reflex.vars.base import CallableVar, LiteralVar, Var
|
|
15
15
|
from reflex.vars.function import FunctionVar
|
|
16
|
+
from reflex.vars.object import ObjectVar
|
|
16
17
|
|
|
17
18
|
SYSTEM_COLOR_MODE: str = "system"
|
|
18
19
|
LIGHT_COLOR_MODE: str = "light"
|
|
@@ -188,7 +189,16 @@ def convert(
|
|
|
188
189
|
out[k] = return_value
|
|
189
190
|
|
|
190
191
|
for key, value in style_dict.items():
|
|
191
|
-
keys =
|
|
192
|
+
keys = (
|
|
193
|
+
format_style_key(key)
|
|
194
|
+
if not isinstance(value, (dict, ObjectVar))
|
|
195
|
+
or (
|
|
196
|
+
isinstance(value, Breakpoints)
|
|
197
|
+
and all(not isinstance(v, dict) for v in value.values())
|
|
198
|
+
)
|
|
199
|
+
else (key,)
|
|
200
|
+
)
|
|
201
|
+
|
|
192
202
|
if isinstance(value, Var):
|
|
193
203
|
return_val = value
|
|
194
204
|
new_var_data = value._get_all_var_data()
|
|
@@ -283,7 +293,7 @@ def _format_emotion_style_pseudo_selector(key: str) -> str:
|
|
|
283
293
|
"""Format a pseudo selector for emotion CSS-in-JS.
|
|
284
294
|
|
|
285
295
|
Args:
|
|
286
|
-
key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover).
|
|
296
|
+
key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover/:hover).
|
|
287
297
|
|
|
288
298
|
Returns:
|
|
289
299
|
A self-referential pseudo selector key (&:hover).
|