reflex 0.5.3a1__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 +3 -3
- 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/html.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/__init__.pyi +2 -1
- reflex/components/el/elements/__init__.py +1 -1
- reflex/components/el/elements/__init__.pyi +3 -2
- 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 +83 -15
- 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/base.py +3 -1
- 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 +20 -6
- reflex/utils/processes.py +27 -0
- reflex/utils/pyi_generator.py +11 -4
- reflex/utils/serializers.py +120 -16
- reflex/utils/types.py +9 -3
- reflex/vars.py +70 -21
- reflex/vars.pyi +2 -2
- {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/METADATA +1 -1
- {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/RECORD +74 -74
- reflex/constants/base.pyi +0 -99
- {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/LICENSE +0 -0
- {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/WHEEL +0 -0
- {reflex-0.5.3a1.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
|
@@ -9,8 +9,7 @@ import re
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
10
10
|
|
|
11
11
|
from reflex import constants
|
|
12
|
-
from reflex.utils import exceptions,
|
|
13
|
-
from reflex.utils.serializers import serialize
|
|
12
|
+
from reflex.utils import exceptions, types
|
|
14
13
|
from reflex.vars import BaseVar, Var
|
|
15
14
|
|
|
16
15
|
if TYPE_CHECKING:
|
|
@@ -400,6 +399,7 @@ def format_prop(
|
|
|
400
399
|
"""
|
|
401
400
|
# import here to avoid circular import.
|
|
402
401
|
from reflex.event import EventChain
|
|
402
|
+
from reflex.utils import serializers
|
|
403
403
|
|
|
404
404
|
try:
|
|
405
405
|
# Handle var props.
|
|
@@ -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.
|
|
@@ -687,6 +694,8 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
|
687
694
|
Raises:
|
|
688
695
|
TypeError: If the given value is not a valid state.
|
|
689
696
|
"""
|
|
697
|
+
from reflex.utils import serializers
|
|
698
|
+
|
|
690
699
|
# Handle dicts.
|
|
691
700
|
if isinstance(value, dict):
|
|
692
701
|
return {k: format_state(v, k) for k, v in value.items()}
|
|
@@ -700,7 +709,7 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
|
700
709
|
return value
|
|
701
710
|
|
|
702
711
|
# Serialize the value.
|
|
703
|
-
serialized = serialize(value)
|
|
712
|
+
serialized = serializers.serialize(value)
|
|
704
713
|
if serialized is not None:
|
|
705
714
|
return serialized
|
|
706
715
|
|
|
@@ -803,7 +812,9 @@ def json_dumps(obj: Any) -> str:
|
|
|
803
812
|
Returns:
|
|
804
813
|
A string
|
|
805
814
|
"""
|
|
806
|
-
|
|
815
|
+
from reflex.utils import serializers
|
|
816
|
+
|
|
817
|
+
return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
|
|
807
818
|
|
|
808
819
|
|
|
809
820
|
def unwrap_vars(value: str) -> str:
|
|
@@ -905,4 +916,7 @@ def format_data_editor_cell(cell: Any):
|
|
|
905
916
|
Returns:
|
|
906
917
|
The formatted cell.
|
|
907
918
|
"""
|
|
908
|
-
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,30 +2,53 @@
|
|
|
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
|
|
15
|
-
from reflex.utils import exceptions,
|
|
29
|
+
from reflex.utils import exceptions, types
|
|
16
30
|
|
|
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)
|
|
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
|
|
82
143
|
|
|
83
144
|
|
|
84
|
-
|
|
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
|
|
|
@@ -154,6 +238,8 @@ def serialize_primitive(value: Union[bool, int, float, None]) -> str:
|
|
|
154
238
|
Returns:
|
|
155
239
|
The serialized number/bool/None.
|
|
156
240
|
"""
|
|
241
|
+
from reflex.utils import format
|
|
242
|
+
|
|
157
243
|
return format.json_dumps(value)
|
|
158
244
|
|
|
159
245
|
|
|
@@ -180,6 +266,8 @@ def serialize_list(value: Union[List, Tuple, Set]) -> str:
|
|
|
180
266
|
Returns:
|
|
181
267
|
The serialized list.
|
|
182
268
|
"""
|
|
269
|
+
from reflex.utils import format
|
|
270
|
+
|
|
183
271
|
# Dump the list to a string.
|
|
184
272
|
fprop = format.json_dumps(list(value))
|
|
185
273
|
|
|
@@ -202,6 +290,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
|
|
|
202
290
|
"""
|
|
203
291
|
# Import here to avoid circular imports.
|
|
204
292
|
from reflex.event import EventHandler
|
|
293
|
+
from reflex.utils import format
|
|
205
294
|
|
|
206
295
|
prop_dict = {}
|
|
207
296
|
|
|
@@ -221,7 +310,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
|
|
|
221
310
|
return format.unwrap_vars(fprop)
|
|
222
311
|
|
|
223
312
|
|
|
224
|
-
@serializer
|
|
313
|
+
@serializer(to=str)
|
|
225
314
|
def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
|
|
226
315
|
"""Serialize a datetime to a JSON string.
|
|
227
316
|
|
|
@@ -234,8 +323,8 @@ def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
|
|
|
234
323
|
return str(dt)
|
|
235
324
|
|
|
236
325
|
|
|
237
|
-
@serializer
|
|
238
|
-
def serialize_path(path: Path):
|
|
326
|
+
@serializer(to=str)
|
|
327
|
+
def serialize_path(path: Path) -> str:
|
|
239
328
|
"""Serialize a pathlib.Path to a JSON string.
|
|
240
329
|
|
|
241
330
|
Args:
|
|
@@ -260,7 +349,7 @@ def serialize_enum(en: Enum) -> str:
|
|
|
260
349
|
return en.value
|
|
261
350
|
|
|
262
351
|
|
|
263
|
-
@serializer
|
|
352
|
+
@serializer(to=str)
|
|
264
353
|
def serialize_color(color: Color) -> str:
|
|
265
354
|
"""Serialize a color.
|
|
266
355
|
|
|
@@ -309,7 +398,7 @@ except ImportError:
|
|
|
309
398
|
pass
|
|
310
399
|
|
|
311
400
|
try:
|
|
312
|
-
from plotly.graph_objects import Figure
|
|
401
|
+
from plotly.graph_objects import Figure, layout
|
|
313
402
|
from plotly.io import to_json
|
|
314
403
|
|
|
315
404
|
@serializer
|
|
@@ -324,6 +413,21 @@ try:
|
|
|
324
413
|
"""
|
|
325
414
|
return json.loads(str(to_json(figure)))
|
|
326
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
|
+
|
|
327
431
|
except ImportError:
|
|
328
432
|
pass
|
|
329
433
|
|
reflex/utils/types.py
CHANGED
|
@@ -42,7 +42,7 @@ from sqlalchemy.orm import (
|
|
|
42
42
|
|
|
43
43
|
from reflex import constants
|
|
44
44
|
from reflex.base import Base
|
|
45
|
-
from reflex.utils import console
|
|
45
|
+
from reflex.utils import console
|
|
46
46
|
|
|
47
47
|
if sys.version_info >= (3, 12):
|
|
48
48
|
from typing import override
|
|
@@ -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_
|
|
@@ -392,6 +396,8 @@ def is_valid_var_type(type_: Type) -> bool:
|
|
|
392
396
|
Returns:
|
|
393
397
|
Whether the type is a valid prop type.
|
|
394
398
|
"""
|
|
399
|
+
from reflex.utils import serializers
|
|
400
|
+
|
|
395
401
|
if is_union(type_):
|
|
396
402
|
return all((is_valid_var_type(arg) for arg in get_args(type_)))
|
|
397
403
|
return _issubclass(type_, StateVar) or serializers.has_serializer(type_)
|
|
@@ -503,7 +509,7 @@ def validate_parameter_literals(func):
|
|
|
503
509
|
annotations = {param[0]: param[1].annotation for param in func_params}
|
|
504
510
|
|
|
505
511
|
# validate args
|
|
506
|
-
for param, arg in zip(annotations
|
|
512
|
+
for param, arg in zip(annotations, args):
|
|
507
513
|
if annotations[param] is inspect.Parameter.empty:
|
|
508
514
|
continue
|
|
509
515
|
validate_literal(param, arg, annotations[param], func.__name__)
|