reflex 0.5.3a2__py3-none-any.whl → 0.5.4__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/apps/demo/code/webui/state.py +3 -2
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +19 -20
- reflex/.templates/web/utils/state.js +6 -0
- reflex/__init__.py +7 -1
- reflex/__init__.pyi +2 -0
- reflex/app.py +2 -5
- reflex/compiler/compiler.py +2 -2
- reflex/components/chakra/base.py +3 -1
- reflex/components/chakra/forms/checkbox.py +1 -1
- reflex/components/chakra/forms/pininput.py +1 -1
- reflex/components/chakra/forms/rangeslider.py +1 -1
- reflex/components/chakra/navigation/link.py +3 -1
- reflex/components/component.py +43 -15
- reflex/components/core/banner.py +2 -1
- reflex/components/core/client_side_routing.py +3 -3
- reflex/components/core/client_side_routing.pyi +1 -0
- reflex/components/core/debounce.py +3 -1
- reflex/components/core/foreach.py +1 -1
- reflex/components/core/upload.py +2 -1
- reflex/components/datadisplay/code.py +4 -1
- reflex/components/datadisplay/dataeditor.py +11 -8
- reflex/components/datadisplay/dataeditor.pyi +1 -0
- reflex/components/el/elements/forms.py +25 -14
- reflex/components/el/elements/forms.pyi +2 -1
- reflex/components/markdown/markdown.py +17 -11
- reflex/components/markdown/markdown.pyi +12 -8
- reflex/components/plotly/plotly.py +82 -14
- reflex/components/plotly/plotly.pyi +15 -82
- reflex/components/radix/primitives/accordion.py +1 -1
- reflex/components/radix/themes/base.py +10 -2
- reflex/components/radix/themes/base.pyi +1 -0
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/components/checkbox.py +3 -1
- reflex/components/radix/themes/components/radio_group.py +6 -4
- reflex/components/radix/themes/components/separator.py +1 -1
- reflex/components/radix/themes/layout/container.py +1 -1
- reflex/components/radix/themes/layout/section.py +1 -1
- reflex/components/recharts/cartesian.py +42 -14
- reflex/components/recharts/cartesian.pyi +81 -17
- reflex/components/recharts/charts.py +12 -21
- reflex/components/recharts/charts.pyi +53 -14
- reflex/components/sonner/toast.py +37 -17
- reflex/components/sonner/toast.pyi +9 -5
- reflex/components/tags/tag.py +2 -1
- reflex/config.py +22 -14
- reflex/constants/__init__.py +2 -0
- reflex/constants/config.py +7 -0
- reflex/event.py +24 -15
- reflex/experimental/__init__.py +22 -2
- reflex/experimental/client_state.py +81 -23
- reflex/experimental/hooks.py +35 -35
- reflex/experimental/layout.py +17 -5
- reflex/experimental/layout.pyi +536 -0
- reflex/reflex.py +9 -5
- reflex/style.py +3 -2
- reflex/testing.py +44 -13
- reflex/utils/compat.py +5 -0
- reflex/utils/format.py +12 -2
- reflex/utils/processes.py +27 -0
- reflex/utils/pyi_generator.py +11 -4
- reflex/utils/serializers.py +114 -15
- reflex/utils/types.py +6 -2
- reflex/vars.py +57 -20
- reflex/vars.pyi +2 -2
- {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/METADATA +1 -1
- {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/RECORD +69 -68
- {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/LICENSE +0 -0
- {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/WHEEL +0 -0
- {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/entry_points.txt +0 -0
reflex/utils/compat.py
CHANGED
|
@@ -21,6 +21,11 @@ def pydantic_v1_patch():
|
|
|
21
21
|
try:
|
|
22
22
|
import pydantic.v1 # type: ignore
|
|
23
23
|
|
|
24
|
+
if pydantic.__version__.startswith("1."):
|
|
25
|
+
# pydantic v1 is already installed
|
|
26
|
+
yield
|
|
27
|
+
return
|
|
28
|
+
|
|
24
29
|
sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore
|
|
25
30
|
sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore
|
|
26
31
|
sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore
|
reflex/utils/format.py
CHANGED
|
@@ -612,6 +612,9 @@ def format_queue_events(
|
|
|
612
612
|
|
|
613
613
|
Returns:
|
|
614
614
|
The compiled javascript callback to queue the given events on the frontend.
|
|
615
|
+
|
|
616
|
+
Raises:
|
|
617
|
+
ValueError: If a lambda function is given which returns a Var.
|
|
615
618
|
"""
|
|
616
619
|
from reflex.event import (
|
|
617
620
|
EventChain,
|
|
@@ -648,7 +651,11 @@ def format_queue_events(
|
|
|
648
651
|
if isinstance(spec, (EventHandler, EventSpec)):
|
|
649
652
|
specs = [call_event_handler(spec, args_spec or _default_args_spec)]
|
|
650
653
|
elif isinstance(spec, type(lambda: None)):
|
|
651
|
-
specs = call_event_fn(spec, args_spec or _default_args_spec)
|
|
654
|
+
specs = call_event_fn(spec, args_spec or _default_args_spec) # type: ignore
|
|
655
|
+
if isinstance(specs, Var):
|
|
656
|
+
raise ValueError(
|
|
657
|
+
f"Invalid event spec: {specs}. Expected a list of EventSpecs."
|
|
658
|
+
)
|
|
652
659
|
payloads.extend(format_event(s) for s in specs)
|
|
653
660
|
|
|
654
661
|
# Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope.
|
|
@@ -909,4 +916,7 @@ def format_data_editor_cell(cell: Any):
|
|
|
909
916
|
Returns:
|
|
910
917
|
The formatted cell.
|
|
911
918
|
"""
|
|
912
|
-
return {
|
|
919
|
+
return {
|
|
920
|
+
"kind": Var.create(value="GridCellKind.Text", _var_is_string=False),
|
|
921
|
+
"data": cell,
|
|
922
|
+
}
|
reflex/utils/processes.py
CHANGED
|
@@ -109,6 +109,33 @@ def change_port(port: str, _type: str) -> str:
|
|
|
109
109
|
return new_port
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
def handle_port(service_name: str, port: str, default_port: str) -> str:
|
|
113
|
+
"""Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg.
|
|
114
|
+
otherwise tell the user the port is in use and exit the app.
|
|
115
|
+
|
|
116
|
+
We make an assumption that when port is the default port,then it hasnt been explicitly set since its not straightforward
|
|
117
|
+
to know whether a port was explicitly provided by the user unless its any other than the default.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
service_name: The frontend or backend.
|
|
121
|
+
port: The provided port.
|
|
122
|
+
default_port: The default port number associated with the specified service.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The port to run the service on.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
Exit:when the port is in use.
|
|
129
|
+
"""
|
|
130
|
+
if is_process_on_port(port):
|
|
131
|
+
if int(port) == int(default_port):
|
|
132
|
+
return change_port(port, service_name)
|
|
133
|
+
else:
|
|
134
|
+
console.error(f"{service_name.capitalize()} port: {port} is already in use")
|
|
135
|
+
raise typer.Exit()
|
|
136
|
+
return port
|
|
137
|
+
|
|
138
|
+
|
|
112
139
|
def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
|
113
140
|
"""Wrapper over subprocess.Popen to unify the launch of child processes.
|
|
114
141
|
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -32,7 +32,7 @@ logger = logging.getLogger("pyi_generator")
|
|
|
32
32
|
PWD = Path(".").resolve()
|
|
33
33
|
|
|
34
34
|
EXCLUDED_FILES = [
|
|
35
|
-
|
|
35
|
+
"app.py",
|
|
36
36
|
"component.py",
|
|
37
37
|
"bare.py",
|
|
38
38
|
"foreach.py",
|
|
@@ -424,7 +424,7 @@ def _generate_component_create_functiondef(
|
|
|
424
424
|
),
|
|
425
425
|
ast.Constant(value=None),
|
|
426
426
|
)
|
|
427
|
-
for trigger in sorted(clz().get_event_triggers()
|
|
427
|
+
for trigger in sorted(clz().get_event_triggers())
|
|
428
428
|
)
|
|
429
429
|
logger.debug(f"Generated {clz.__name__}.create method with {len(kwargs)} kwargs")
|
|
430
430
|
create_args = ast.arguments(
|
|
@@ -488,7 +488,9 @@ def _generate_staticmethod_call_functiondef(
|
|
|
488
488
|
kwonlyargs=[],
|
|
489
489
|
kw_defaults=[],
|
|
490
490
|
kwarg=ast.arg(arg="props"),
|
|
491
|
-
defaults=[]
|
|
491
|
+
defaults=[ast.Constant(value=default) for default in fullspec.defaults]
|
|
492
|
+
if fullspec.defaults
|
|
493
|
+
else [],
|
|
492
494
|
)
|
|
493
495
|
definition = ast.FunctionDef(
|
|
494
496
|
name="__call__",
|
|
@@ -854,7 +856,11 @@ class PyiGenerator:
|
|
|
854
856
|
mode=black.mode.Mode(is_pyi=True),
|
|
855
857
|
).splitlines():
|
|
856
858
|
# Bit of a hack here, since the AST cannot represent comments.
|
|
857
|
-
if
|
|
859
|
+
if (
|
|
860
|
+
"def create(" in formatted_line
|
|
861
|
+
or "Figure" in formatted_line
|
|
862
|
+
or "Var[Template]" in formatted_line
|
|
863
|
+
):
|
|
858
864
|
pyi_content.append(formatted_line + " # type: ignore")
|
|
859
865
|
else:
|
|
860
866
|
pyi_content.append(formatted_line)
|
|
@@ -954,6 +960,7 @@ class PyiGenerator:
|
|
|
954
960
|
target_path.is_file()
|
|
955
961
|
and target_path.suffix == ".py"
|
|
956
962
|
and target_path.name not in EXCLUDED_FILES
|
|
963
|
+
and "reflex/components" in str(target_path)
|
|
957
964
|
):
|
|
958
965
|
file_targets.append(target_path)
|
|
959
966
|
continue
|
reflex/utils/serializers.py
CHANGED
|
@@ -2,13 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import functools
|
|
5
6
|
import json
|
|
6
7
|
import types as builtin_types
|
|
7
8
|
import warnings
|
|
8
9
|
from datetime import date, datetime, time, timedelta
|
|
9
10
|
from enum import Enum
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
12
|
+
from typing import (
|
|
13
|
+
Any,
|
|
14
|
+
Callable,
|
|
15
|
+
Dict,
|
|
16
|
+
List,
|
|
17
|
+
Literal,
|
|
18
|
+
Optional,
|
|
19
|
+
Set,
|
|
20
|
+
Tuple,
|
|
21
|
+
Type,
|
|
22
|
+
Union,
|
|
23
|
+
get_type_hints,
|
|
24
|
+
overload,
|
|
25
|
+
)
|
|
12
26
|
|
|
13
27
|
from reflex.base import Base
|
|
14
28
|
from reflex.constants.colors import Color, format_color
|
|
@@ -17,15 +31,24 @@ from reflex.utils import exceptions, types
|
|
|
17
31
|
# Mapping from type to a serializer.
|
|
18
32
|
# The serializer should convert the type to a JSON object.
|
|
19
33
|
SerializedType = Union[str, bool, int, float, list, dict]
|
|
34
|
+
|
|
35
|
+
|
|
20
36
|
Serializer = Callable[[Type], SerializedType]
|
|
37
|
+
|
|
38
|
+
|
|
21
39
|
SERIALIZERS: dict[Type, Serializer] = {}
|
|
40
|
+
SERIALIZER_TYPES: dict[Type, Type] = {}
|
|
22
41
|
|
|
23
42
|
|
|
24
|
-
def serializer(
|
|
43
|
+
def serializer(
|
|
44
|
+
fn: Serializer | None = None,
|
|
45
|
+
to: Type | None = None,
|
|
46
|
+
) -> Serializer:
|
|
25
47
|
"""Decorator to add a serializer for a given type.
|
|
26
48
|
|
|
27
49
|
Args:
|
|
28
50
|
fn: The function to decorate.
|
|
51
|
+
to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string.
|
|
29
52
|
|
|
30
53
|
Returns:
|
|
31
54
|
The decorated function.
|
|
@@ -33,8 +56,9 @@ def serializer(fn: Serializer) -> Serializer:
|
|
|
33
56
|
Raises:
|
|
34
57
|
ValueError: If the function does not take a single argument.
|
|
35
58
|
"""
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
if fn is None:
|
|
60
|
+
# If the function is not provided, return a partial that acts as a decorator.
|
|
61
|
+
return functools.partial(serializer, to=to) # type: ignore
|
|
38
62
|
|
|
39
63
|
# Check the type hints to get the type of the argument.
|
|
40
64
|
type_hints = get_type_hints(fn)
|
|
@@ -54,18 +78,47 @@ def serializer(fn: Serializer) -> Serializer:
|
|
|
54
78
|
f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
|
|
55
79
|
)
|
|
56
80
|
|
|
81
|
+
# Apply type transformation if requested
|
|
82
|
+
if to is not None:
|
|
83
|
+
SERIALIZER_TYPES[type_] = to
|
|
84
|
+
get_serializer_type.cache_clear()
|
|
85
|
+
|
|
57
86
|
# Register the serializer.
|
|
58
87
|
SERIALIZERS[type_] = fn
|
|
88
|
+
get_serializer.cache_clear()
|
|
59
89
|
|
|
60
90
|
# Return the function.
|
|
61
91
|
return fn
|
|
62
92
|
|
|
63
93
|
|
|
64
|
-
|
|
94
|
+
@overload
|
|
95
|
+
def serialize(
|
|
96
|
+
value: Any, get_type: Literal[True]
|
|
97
|
+
) -> Tuple[Optional[SerializedType], Optional[types.GenericType]]:
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@overload
|
|
102
|
+
def serialize(value: Any, get_type: Literal[False]) -> Optional[SerializedType]:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@overload
|
|
107
|
+
def serialize(value: Any) -> Optional[SerializedType]:
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def serialize(
|
|
112
|
+
value: Any, get_type: bool = False
|
|
113
|
+
) -> Union[
|
|
114
|
+
Optional[SerializedType],
|
|
115
|
+
Tuple[Optional[SerializedType], Optional[types.GenericType]],
|
|
116
|
+
]:
|
|
65
117
|
"""Serialize the value to a JSON string.
|
|
66
118
|
|
|
67
119
|
Args:
|
|
68
120
|
value: The value to serialize.
|
|
121
|
+
get_type: Whether to return the type of the serialized value.
|
|
69
122
|
|
|
70
123
|
Returns:
|
|
71
124
|
The serialized value, or None if a serializer is not found.
|
|
@@ -75,13 +128,22 @@ def serialize(value: Any) -> SerializedType | None:
|
|
|
75
128
|
|
|
76
129
|
# If there is no serializer, return None.
|
|
77
130
|
if serializer is None:
|
|
131
|
+
if get_type:
|
|
132
|
+
return None, None
|
|
78
133
|
return None
|
|
79
134
|
|
|
80
135
|
# Serialize the value.
|
|
81
|
-
|
|
136
|
+
serialized = serializer(value)
|
|
82
137
|
|
|
138
|
+
# Return the serialized value and the type.
|
|
139
|
+
if get_type:
|
|
140
|
+
return serialized, get_serializer_type(type(value))
|
|
141
|
+
else:
|
|
142
|
+
return serialized
|
|
83
143
|
|
|
84
|
-
|
|
144
|
+
|
|
145
|
+
@functools.lru_cache
|
|
146
|
+
def get_serializer(type_: Type) -> Optional[Serializer]:
|
|
85
147
|
"""Get the serializer for the type.
|
|
86
148
|
|
|
87
149
|
Args:
|
|
@@ -90,8 +152,6 @@ def get_serializer(type_: Type) -> Serializer | None:
|
|
|
90
152
|
Returns:
|
|
91
153
|
The serializer for the type, or None if there is no serializer.
|
|
92
154
|
"""
|
|
93
|
-
global SERIALIZERS
|
|
94
|
-
|
|
95
155
|
# First, check if the type is registered.
|
|
96
156
|
serializer = SERIALIZERS.get(type_)
|
|
97
157
|
if serializer is not None:
|
|
@@ -106,6 +166,30 @@ def get_serializer(type_: Type) -> Serializer | None:
|
|
|
106
166
|
return None
|
|
107
167
|
|
|
108
168
|
|
|
169
|
+
@functools.lru_cache
|
|
170
|
+
def get_serializer_type(type_: Type) -> Optional[Type]:
|
|
171
|
+
"""Get the converted type for the type after serializing.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
type_: The type to get the serializer type for.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
The serialized type for the type, or None if there is no type conversion registered.
|
|
178
|
+
"""
|
|
179
|
+
# First, check if the type is registered.
|
|
180
|
+
serializer = SERIALIZER_TYPES.get(type_)
|
|
181
|
+
if serializer is not None:
|
|
182
|
+
return serializer
|
|
183
|
+
|
|
184
|
+
# If the type is not registered, check if it is a subclass of a registered type.
|
|
185
|
+
for registered_type, serializer in reversed(SERIALIZER_TYPES.items()):
|
|
186
|
+
if types._issubclass(type_, registered_type):
|
|
187
|
+
return serializer
|
|
188
|
+
|
|
189
|
+
# If there is no serializer, return None.
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
109
193
|
def has_serializer(type_: Type) -> bool:
|
|
110
194
|
"""Check if there is a serializer for the type.
|
|
111
195
|
|
|
@@ -118,7 +202,7 @@ def has_serializer(type_: Type) -> bool:
|
|
|
118
202
|
return get_serializer(type_) is not None
|
|
119
203
|
|
|
120
204
|
|
|
121
|
-
@serializer
|
|
205
|
+
@serializer(to=str)
|
|
122
206
|
def serialize_type(value: type) -> str:
|
|
123
207
|
"""Serialize a python type.
|
|
124
208
|
|
|
@@ -226,7 +310,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
|
|
|
226
310
|
return format.unwrap_vars(fprop)
|
|
227
311
|
|
|
228
312
|
|
|
229
|
-
@serializer
|
|
313
|
+
@serializer(to=str)
|
|
230
314
|
def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
|
|
231
315
|
"""Serialize a datetime to a JSON string.
|
|
232
316
|
|
|
@@ -239,8 +323,8 @@ def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
|
|
|
239
323
|
return str(dt)
|
|
240
324
|
|
|
241
325
|
|
|
242
|
-
@serializer
|
|
243
|
-
def serialize_path(path: Path):
|
|
326
|
+
@serializer(to=str)
|
|
327
|
+
def serialize_path(path: Path) -> str:
|
|
244
328
|
"""Serialize a pathlib.Path to a JSON string.
|
|
245
329
|
|
|
246
330
|
Args:
|
|
@@ -265,7 +349,7 @@ def serialize_enum(en: Enum) -> str:
|
|
|
265
349
|
return en.value
|
|
266
350
|
|
|
267
351
|
|
|
268
|
-
@serializer
|
|
352
|
+
@serializer(to=str)
|
|
269
353
|
def serialize_color(color: Color) -> str:
|
|
270
354
|
"""Serialize a color.
|
|
271
355
|
|
|
@@ -314,7 +398,7 @@ except ImportError:
|
|
|
314
398
|
pass
|
|
315
399
|
|
|
316
400
|
try:
|
|
317
|
-
from plotly.graph_objects import Figure
|
|
401
|
+
from plotly.graph_objects import Figure, layout
|
|
318
402
|
from plotly.io import to_json
|
|
319
403
|
|
|
320
404
|
@serializer
|
|
@@ -329,6 +413,21 @@ try:
|
|
|
329
413
|
"""
|
|
330
414
|
return json.loads(str(to_json(figure)))
|
|
331
415
|
|
|
416
|
+
@serializer
|
|
417
|
+
def serialize_template(template: layout.Template) -> dict:
|
|
418
|
+
"""Serialize a plotly template.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
template: The template to serialize.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
The serialized template.
|
|
425
|
+
"""
|
|
426
|
+
return {
|
|
427
|
+
"data": json.loads(str(to_json(template.data))),
|
|
428
|
+
"layout": json.loads(str(to_json(template.layout))),
|
|
429
|
+
}
|
|
430
|
+
|
|
332
431
|
except ImportError:
|
|
333
432
|
pass
|
|
334
433
|
|
reflex/utils/types.py
CHANGED
|
@@ -215,7 +215,11 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
215
215
|
attr = getattr(cls, name, None)
|
|
216
216
|
if hint := get_property_hint(attr):
|
|
217
217
|
return hint
|
|
218
|
-
if
|
|
218
|
+
if (
|
|
219
|
+
hasattr(cls, "__fields__")
|
|
220
|
+
and name in cls.__fields__
|
|
221
|
+
and hasattr(cls.__fields__[name], "outer_type_")
|
|
222
|
+
):
|
|
219
223
|
# pydantic models
|
|
220
224
|
field = cls.__fields__[name]
|
|
221
225
|
type_ = field.outer_type_
|
|
@@ -505,7 +509,7 @@ def validate_parameter_literals(func):
|
|
|
505
509
|
annotations = {param[0]: param[1].annotation for param in func_params}
|
|
506
510
|
|
|
507
511
|
# validate args
|
|
508
|
-
for param, arg in zip(annotations
|
|
512
|
+
for param, arg in zip(annotations, args):
|
|
509
513
|
if annotations[param] is inspect.Parameter.empty:
|
|
510
514
|
continue
|
|
511
515
|
validate_literal(param, arg, annotations[param], func.__name__)
|
reflex/vars.py
CHANGED
|
@@ -347,7 +347,7 @@ class Var:
|
|
|
347
347
|
cls,
|
|
348
348
|
value: Any,
|
|
349
349
|
_var_is_local: bool = True,
|
|
350
|
-
_var_is_string: bool =
|
|
350
|
+
_var_is_string: bool | None = None,
|
|
351
351
|
_var_data: Optional[VarData] = None,
|
|
352
352
|
) -> Var | None:
|
|
353
353
|
"""Create a var from a value.
|
|
@@ -380,18 +380,39 @@ class Var:
|
|
|
380
380
|
|
|
381
381
|
# Try to serialize the value.
|
|
382
382
|
type_ = type(value)
|
|
383
|
-
|
|
383
|
+
if type_ in types.JSONType:
|
|
384
|
+
name = value
|
|
385
|
+
else:
|
|
386
|
+
name, serialized_type = serializers.serialize(value, get_type=True)
|
|
387
|
+
if (
|
|
388
|
+
serialized_type is not None
|
|
389
|
+
and _var_is_string is None
|
|
390
|
+
and issubclass(serialized_type, str)
|
|
391
|
+
):
|
|
392
|
+
_var_is_string = True
|
|
384
393
|
if name is None:
|
|
385
394
|
raise VarTypeError(
|
|
386
395
|
f"No JSON serializer found for var {value} of type {type_}."
|
|
387
396
|
)
|
|
388
397
|
name = name if isinstance(name, str) else format.json_dumps(name)
|
|
389
398
|
|
|
399
|
+
if _var_is_string is None and type_ is str:
|
|
400
|
+
console.deprecate(
|
|
401
|
+
feature_name="Creating a Var from a string without specifying _var_is_string",
|
|
402
|
+
reason=(
|
|
403
|
+
"Specify _var_is_string=False to create a Var that is not a string literal. "
|
|
404
|
+
"In the future, creating a Var from a string will be treated as a string literal "
|
|
405
|
+
"by default."
|
|
406
|
+
),
|
|
407
|
+
deprecation_version="0.5.4",
|
|
408
|
+
removal_version="0.6.0",
|
|
409
|
+
)
|
|
410
|
+
|
|
390
411
|
return BaseVar(
|
|
391
412
|
_var_name=name,
|
|
392
413
|
_var_type=type_,
|
|
393
414
|
_var_is_local=_var_is_local,
|
|
394
|
-
_var_is_string=_var_is_string,
|
|
415
|
+
_var_is_string=_var_is_string if _var_is_string is not None else False,
|
|
395
416
|
_var_data=_var_data,
|
|
396
417
|
)
|
|
397
418
|
|
|
@@ -400,7 +421,7 @@ class Var:
|
|
|
400
421
|
cls,
|
|
401
422
|
value: Any,
|
|
402
423
|
_var_is_local: bool = True,
|
|
403
|
-
_var_is_string: bool =
|
|
424
|
+
_var_is_string: bool | None = None,
|
|
404
425
|
_var_data: Optional[VarData] = None,
|
|
405
426
|
) -> Var:
|
|
406
427
|
"""Create a var from a value, asserting that it is not None.
|
|
@@ -514,7 +535,7 @@ class Var:
|
|
|
514
535
|
if other is None:
|
|
515
536
|
return self._replace()
|
|
516
537
|
if not isinstance(other, Var):
|
|
517
|
-
other = Var.create(other)
|
|
538
|
+
other = Var.create(other, _var_is_string=False)
|
|
518
539
|
return self._replace(
|
|
519
540
|
_var_name=f"{{...{self._var_name}, ...{other._var_name}}}" # type: ignore
|
|
520
541
|
)
|
|
@@ -531,6 +552,14 @@ class Var:
|
|
|
531
552
|
fn = "JSON.stringify" if json else "String"
|
|
532
553
|
return self.operation(fn=fn, type_=str)
|
|
533
554
|
|
|
555
|
+
def to_int(self) -> Var:
|
|
556
|
+
"""Convert a var to an int.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
The parseInt var.
|
|
560
|
+
"""
|
|
561
|
+
return self.operation(fn="parseInt", type_=int)
|
|
562
|
+
|
|
534
563
|
def __hash__(self) -> int:
|
|
535
564
|
"""Define a hash function for a var.
|
|
536
565
|
|
|
@@ -802,9 +831,9 @@ class Var:
|
|
|
802
831
|
from reflex.utils import format
|
|
803
832
|
|
|
804
833
|
if isinstance(other, str):
|
|
805
|
-
other = Var.create(json.dumps(other))
|
|
834
|
+
other = Var.create(json.dumps(other), _var_is_string=False)
|
|
806
835
|
else:
|
|
807
|
-
other = Var.create(other)
|
|
836
|
+
other = Var.create(other, _var_is_string=False)
|
|
808
837
|
|
|
809
838
|
type_ = type_ or self._var_type
|
|
810
839
|
|
|
@@ -847,19 +876,19 @@ class Var:
|
|
|
847
876
|
if invoke_fn:
|
|
848
877
|
# invoke the function on left operand.
|
|
849
878
|
operation_name = (
|
|
850
|
-
f"{left_operand_full_name}.{fn}({right_operand_full_name})"
|
|
851
|
-
)
|
|
879
|
+
f"{left_operand_full_name}.{fn}({right_operand_full_name})" # type: ignore
|
|
880
|
+
)
|
|
852
881
|
else:
|
|
853
882
|
# pass the operands as arguments to the function.
|
|
854
883
|
operation_name = (
|
|
855
|
-
f"{left_operand_full_name} {op} {right_operand_full_name}"
|
|
856
|
-
)
|
|
884
|
+
f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
|
|
885
|
+
)
|
|
857
886
|
operation_name = f"{fn}({operation_name})"
|
|
858
887
|
else:
|
|
859
888
|
# apply operator to operands (left operand <operator> right_operand)
|
|
860
889
|
operation_name = (
|
|
861
|
-
f"{left_operand_full_name} {op} {right_operand_full_name}"
|
|
862
|
-
)
|
|
890
|
+
f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
|
|
891
|
+
)
|
|
863
892
|
operation_name = format.wrap(operation_name, "(")
|
|
864
893
|
else:
|
|
865
894
|
# apply operator to left operand (<operator> left_operand)
|
|
@@ -1387,7 +1416,7 @@ class Var:
|
|
|
1387
1416
|
if isinstance(other, str):
|
|
1388
1417
|
other = Var.create(json.dumps(other), _var_is_string=True)
|
|
1389
1418
|
elif not isinstance(other, Var):
|
|
1390
|
-
other = Var.create(other)
|
|
1419
|
+
other = Var.create(other, _var_is_string=False)
|
|
1391
1420
|
if types._issubclass(self._var_type, Dict):
|
|
1392
1421
|
return self._replace(
|
|
1393
1422
|
_var_name=f"{self._var_name}.{method}({other._var_full_name})",
|
|
@@ -1491,7 +1520,11 @@ class Var:
|
|
|
1491
1520
|
if not types._issubclass(self._var_type, str):
|
|
1492
1521
|
raise VarTypeError(f"Cannot strip non-string var {self._var_full_name}.")
|
|
1493
1522
|
|
|
1494
|
-
other =
|
|
1523
|
+
other = (
|
|
1524
|
+
Var.create_safe(json.dumps(other), _var_is_string=False)
|
|
1525
|
+
if isinstance(other, str)
|
|
1526
|
+
else other
|
|
1527
|
+
)
|
|
1495
1528
|
|
|
1496
1529
|
return self._replace(
|
|
1497
1530
|
_var_name=f"{self._var_name}.replace(/^${other._var_full_name}|${other._var_full_name}$/g, '')",
|
|
@@ -1514,7 +1547,11 @@ class Var:
|
|
|
1514
1547
|
if not types._issubclass(self._var_type, str):
|
|
1515
1548
|
raise VarTypeError(f"Cannot split non-string var {self._var_full_name}.")
|
|
1516
1549
|
|
|
1517
|
-
other =
|
|
1550
|
+
other = (
|
|
1551
|
+
Var.create_safe(json.dumps(other), _var_is_string=False)
|
|
1552
|
+
if isinstance(other, str)
|
|
1553
|
+
else other
|
|
1554
|
+
)
|
|
1518
1555
|
|
|
1519
1556
|
return self._replace(
|
|
1520
1557
|
_var_name=f"{self._var_name}.split({other._var_full_name})",
|
|
@@ -1539,11 +1576,11 @@ class Var:
|
|
|
1539
1576
|
raise VarTypeError(f"Cannot join non-list var {self._var_full_name}.")
|
|
1540
1577
|
|
|
1541
1578
|
if other is None:
|
|
1542
|
-
other = Var.create_safe('""')
|
|
1579
|
+
other = Var.create_safe('""', _var_is_string=False)
|
|
1543
1580
|
if isinstance(other, str):
|
|
1544
|
-
other = Var.create_safe(json.dumps(other))
|
|
1581
|
+
other = Var.create_safe(json.dumps(other), _var_is_string=False)
|
|
1545
1582
|
else:
|
|
1546
|
-
other = Var.create_safe(other)
|
|
1583
|
+
other = Var.create_safe(other, _var_is_string=False)
|
|
1547
1584
|
|
|
1548
1585
|
return self._replace(
|
|
1549
1586
|
_var_name=f"{self._var_name}.join({other._var_full_name})",
|
|
@@ -1612,7 +1649,7 @@ class Var:
|
|
|
1612
1649
|
if not isinstance(v2, Var):
|
|
1613
1650
|
v2 = Var.create(v2)
|
|
1614
1651
|
if v2 is None:
|
|
1615
|
-
v2 = Var.create_safe("undefined")
|
|
1652
|
+
v2 = Var.create_safe("undefined", _var_is_string=False)
|
|
1616
1653
|
elif v2._var_type != int:
|
|
1617
1654
|
raise VarTypeError(f"Cannot get range on non-int var {v2._var_full_name}.")
|
|
1618
1655
|
|
reflex/vars.pyi
CHANGED
|
@@ -51,11 +51,11 @@ class Var:
|
|
|
51
51
|
_var_data: VarData | None = None
|
|
52
52
|
@classmethod
|
|
53
53
|
def create(
|
|
54
|
-
cls, value: Any, _var_is_local: bool =
|
|
54
|
+
cls, value: Any, _var_is_local: bool = True, _var_is_string: bool | None = None, _var_data: VarData | None = None,
|
|
55
55
|
) -> Optional[Var]: ...
|
|
56
56
|
@classmethod
|
|
57
57
|
def create_safe(
|
|
58
|
-
cls, value: Any, _var_is_local: bool =
|
|
58
|
+
cls, value: Any, _var_is_local: bool = True, _var_is_string: bool | None = None, _var_data: VarData | None = None,
|
|
59
59
|
) -> Var: ...
|
|
60
60
|
@classmethod
|
|
61
61
|
def __class_getitem__(cls, type_: Type) -> _GenericAlias: ...
|