reflex 0.6.2.post1__py3-none-any.whl → 0.6.3__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/__init__.py +3 -2
- reflex/__init__.pyi +3 -1
- reflex/components/base/app_wrap.pyi +17 -37
- reflex/components/base/body.pyi +17 -37
- reflex/components/base/document.pyi +77 -177
- reflex/components/base/error_boundary.pyi +18 -38
- reflex/components/base/fragment.pyi +17 -37
- reflex/components/base/head.pyi +32 -72
- reflex/components/base/link.pyi +32 -72
- reflex/components/base/meta.pyi +62 -142
- reflex/components/base/script.py +4 -4
- reflex/components/base/script.pyi +20 -40
- reflex/components/component.py +30 -19
- reflex/components/core/banner.pyi +77 -177
- reflex/components/core/client_side_routing.pyi +32 -72
- reflex/components/core/clipboard.py +3 -3
- reflex/components/core/clipboard.pyi +18 -38
- reflex/components/core/debounce.py +2 -2
- reflex/components/core/debounce.pyi +18 -38
- reflex/components/core/html.pyi +17 -37
- reflex/components/core/upload.py +4 -5
- reflex/components/core/upload.pyi +65 -145
- reflex/components/datadisplay/code.pyi +32 -72
- reflex/components/datadisplay/dataeditor.py +13 -13
- reflex/components/datadisplay/dataeditor.pyi +33 -83
- reflex/components/dynamic.py +6 -7
- reflex/components/el/__init__.pyi +1 -0
- reflex/components/el/element.pyi +17 -37
- reflex/components/el/elements/base.pyi +17 -37
- reflex/components/el/elements/forms.py +29 -14
- reflex/components/el/elements/forms.pyi +222 -504
- reflex/components/el/elements/inline.pyi +422 -982
- reflex/components/el/elements/media.pyi +377 -877
- reflex/components/el/elements/metadata.pyi +92 -212
- reflex/components/el/elements/other.pyi +107 -247
- reflex/components/el/elements/scripts.pyi +47 -107
- reflex/components/el/elements/sectioning.pyi +227 -527
- reflex/components/el/elements/tables.pyi +152 -352
- reflex/components/el/elements/typography.pyi +227 -527
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +32 -72
- reflex/components/lucide/icon.pyi +32 -72
- reflex/components/markdown/markdown.pyi +16 -36
- reflex/components/moment/moment.py +2 -2
- reflex/components/moment/moment.pyi +18 -38
- reflex/components/next/base.pyi +17 -37
- reflex/components/next/image.py +3 -3
- reflex/components/next/image.pyi +19 -39
- reflex/components/next/link.pyi +17 -37
- reflex/components/next/video.pyi +17 -37
- reflex/components/plotly/plotly.py +1 -1
- reflex/components/plotly/plotly.pyi +35 -87
- reflex/components/radix/primitives/accordion.py +14 -2
- reflex/components/radix/primitives/accordion.pyi +110 -250
- reflex/components/radix/primitives/base.pyi +32 -72
- reflex/components/radix/primitives/drawer.py +30 -12
- reflex/components/radix/primitives/drawer.pyi +159 -373
- reflex/components/radix/primitives/form.py +3 -3
- reflex/components/radix/primitives/form.pyi +158 -364
- reflex/components/radix/primitives/progress.pyi +77 -177
- reflex/components/radix/primitives/slider.py +17 -3
- reflex/components/radix/primitives/slider.pyi +81 -183
- reflex/components/radix/themes/base.pyi +107 -247
- reflex/components/radix/themes/color_mode.pyi +48 -117
- reflex/components/radix/themes/components/alert_dialog.py +5 -5
- reflex/components/radix/themes/components/alert_dialog.pyi +111 -259
- reflex/components/radix/themes/components/aspect_ratio.pyi +17 -37
- reflex/components/radix/themes/components/avatar.pyi +17 -37
- reflex/components/radix/themes/components/badge.pyi +17 -37
- reflex/components/radix/themes/components/button.pyi +17 -37
- reflex/components/radix/themes/components/callout.pyi +77 -177
- reflex/components/radix/themes/components/card.pyi +17 -37
- reflex/components/radix/themes/components/checkbox.py +3 -3
- reflex/components/radix/themes/components/checkbox.pyi +50 -110
- reflex/components/radix/themes/components/checkbox_cards.pyi +32 -72
- reflex/components/radix/themes/components/checkbox_group.pyi +32 -72
- reflex/components/radix/themes/components/context_menu.py +11 -11
- reflex/components/radix/themes/components/context_menu.pyi +132 -312
- reflex/components/radix/themes/components/data_list.pyi +62 -142
- reflex/components/radix/themes/components/dialog.py +7 -7
- reflex/components/radix/themes/components/dialog.pyi +114 -268
- reflex/components/radix/themes/components/dropdown_menu.py +13 -13
- reflex/components/radix/themes/components/dropdown_menu.pyi +134 -316
- reflex/components/radix/themes/components/hover_card.py +2 -2
- reflex/components/radix/themes/components/hover_card.pyi +64 -148
- reflex/components/radix/themes/components/icon_button.pyi +17 -37
- reflex/components/radix/themes/components/inset.pyi +17 -37
- reflex/components/radix/themes/components/popover.py +8 -8
- reflex/components/radix/themes/components/popover.pyi +69 -163
- reflex/components/radix/themes/components/progress.pyi +17 -37
- reflex/components/radix/themes/components/radio.pyi +17 -37
- reflex/components/radix/themes/components/radio_cards.py +2 -2
- reflex/components/radix/themes/components/radio_cards.pyi +33 -75
- reflex/components/radix/themes/components/radio_group.py +4 -4
- reflex/components/radix/themes/components/radio_group.pyi +63 -143
- reflex/components/radix/themes/components/scroll_area.pyi +17 -37
- reflex/components/radix/themes/components/segmented_control.py +14 -2
- reflex/components/radix/themes/components/segmented_control.pyi +35 -73
- reflex/components/radix/themes/components/select.py +6 -5
- reflex/components/radix/themes/components/select.pyi +146 -338
- reflex/components/radix/themes/components/separator.pyi +17 -37
- reflex/components/radix/themes/components/skeleton.pyi +17 -37
- reflex/components/radix/themes/components/slider.py +19 -3
- reflex/components/radix/themes/components/slider.pyi +23 -41
- reflex/components/radix/themes/components/spinner.pyi +17 -37
- reflex/components/radix/themes/components/switch.py +2 -2
- reflex/components/radix/themes/components/switch.pyi +18 -38
- reflex/components/radix/themes/components/table.pyi +107 -247
- reflex/components/radix/themes/components/tabs.py +2 -2
- reflex/components/radix/themes/components/tabs.pyi +79 -179
- reflex/components/radix/themes/components/text_area.py +0 -16
- reflex/components/radix/themes/components/text_area.pyi +20 -42
- reflex/components/radix/themes/components/text_field.py +6 -6
- reflex/components/radix/themes/components/text_field.pyi +53 -117
- reflex/components/radix/themes/components/tooltip.py +4 -4
- reflex/components/radix/themes/components/tooltip.pyi +20 -46
- reflex/components/radix/themes/layout/base.pyi +17 -37
- reflex/components/radix/themes/layout/box.pyi +17 -37
- reflex/components/radix/themes/layout/center.pyi +17 -37
- reflex/components/radix/themes/layout/container.pyi +17 -37
- reflex/components/radix/themes/layout/flex.pyi +17 -37
- reflex/components/radix/themes/layout/grid.pyi +17 -37
- reflex/components/radix/themes/layout/list.pyi +77 -177
- reflex/components/radix/themes/layout/section.pyi +17 -37
- reflex/components/radix/themes/layout/spacer.pyi +17 -37
- reflex/components/radix/themes/layout/stack.pyi +47 -107
- reflex/components/radix/themes/typography/blockquote.pyi +17 -37
- reflex/components/radix/themes/typography/code.pyi +17 -37
- reflex/components/radix/themes/typography/heading.pyi +17 -37
- reflex/components/radix/themes/typography/link.pyi +17 -37
- reflex/components/radix/themes/typography/text.pyi +107 -247
- reflex/components/react_player/audio.pyi +33 -69
- reflex/components/react_player/react_player.py +17 -17
- reflex/components/react_player/react_player.pyi +33 -69
- reflex/components/react_player/video.pyi +33 -69
- reflex/components/recharts/cartesian.py +216 -186
- reflex/components/recharts/cartesian.pyi +623 -832
- reflex/components/recharts/charts.py +68 -65
- reflex/components/recharts/charts.pyi +213 -433
- reflex/components/recharts/general.py +27 -21
- reflex/components/recharts/general.pyi +94 -189
- reflex/components/recharts/polar.py +135 -97
- reflex/components/recharts/polar.pyi +219 -229
- reflex/components/recharts/recharts.py +5 -1
- reflex/components/recharts/recharts.pyi +37 -73
- reflex/components/sonner/toast.py +1 -1
- reflex/components/sonner/toast.pyi +17 -37
- reflex/components/suneditor/editor.pyi +26 -52
- reflex/components/tags/iter_tag.py +2 -2
- reflex/config.py +16 -0
- reflex/constants/__init__.py +2 -0
- reflex/constants/compiler.py +25 -0
- reflex/constants/installer.py +17 -16
- reflex/constants/state.py +11 -0
- reflex/constants/style.py +1 -1
- reflex/event.py +337 -84
- reflex/experimental/layout.pyi +78 -180
- reflex/istate/dynamic.py +3 -0
- reflex/state.py +197 -118
- reflex/style.py +5 -0
- reflex/testing.py +8 -5
- reflex/utils/compat.py +1 -3
- reflex/utils/exceptions.py +8 -0
- reflex/utils/path_ops.py +2 -2
- reflex/utils/prerequisites.py +2 -2
- reflex/utils/pyi_generator.py +44 -4
- reflex/utils/registry.py +17 -3
- reflex/utils/telemetry.py +1 -3
- reflex/utils/types.py +60 -16
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +127 -72
- reflex/vars/object.py +5 -1
- reflex/vars/sequence.py +15 -3
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3.dist-info}/METADATA +3 -3
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3.dist-info}/RECORD +178 -176
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3.dist-info}/LICENSE +0 -0
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3.dist-info}/WHEEL +0 -0
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3.dist-info}/entry_points.txt +0 -0
reflex/state.py
CHANGED
|
@@ -10,6 +10,7 @@ import functools
|
|
|
10
10
|
import inspect
|
|
11
11
|
import os
|
|
12
12
|
import pickle
|
|
13
|
+
import sys
|
|
13
14
|
import uuid
|
|
14
15
|
from abc import ABC, abstractmethod
|
|
15
16
|
from collections import defaultdict
|
|
@@ -32,6 +33,7 @@ from typing import (
|
|
|
32
33
|
Type,
|
|
33
34
|
Union,
|
|
34
35
|
cast,
|
|
36
|
+
get_args,
|
|
35
37
|
get_type_hints,
|
|
36
38
|
)
|
|
37
39
|
|
|
@@ -59,6 +61,7 @@ import wrapt
|
|
|
59
61
|
from redis.asyncio import Redis
|
|
60
62
|
from redis.exceptions import ResponseError
|
|
61
63
|
|
|
64
|
+
import reflex.istate.dynamic
|
|
62
65
|
from reflex import constants
|
|
63
66
|
from reflex.base import Base
|
|
64
67
|
from reflex.event import (
|
|
@@ -75,13 +78,14 @@ from reflex.utils.exceptions import (
|
|
|
75
78
|
DynamicRouteArgShadowsStateVar,
|
|
76
79
|
EventHandlerShadowsBuiltInStateMethod,
|
|
77
80
|
ImmutableStateError,
|
|
81
|
+
InvalidStateManagerMode,
|
|
78
82
|
LockExpiredError,
|
|
79
83
|
SetUndefinedStateVarError,
|
|
80
84
|
StateSchemaMismatchError,
|
|
81
85
|
)
|
|
82
86
|
from reflex.utils.exec import is_testing_env
|
|
83
87
|
from reflex.utils.serializers import serializer
|
|
84
|
-
from reflex.utils.types import override
|
|
88
|
+
from reflex.utils.types import get_origin, override
|
|
85
89
|
from reflex.vars import VarData
|
|
86
90
|
|
|
87
91
|
if TYPE_CHECKING:
|
|
@@ -240,12 +244,16 @@ def get_var_for_field(cls: Type[BaseState], f: ModelField):
|
|
|
240
244
|
Returns:
|
|
241
245
|
The Var instance.
|
|
242
246
|
"""
|
|
247
|
+
from reflex.vars import Field
|
|
248
|
+
|
|
243
249
|
field_name = format.format_state_name(cls.get_full_name()) + "." + f.name
|
|
244
250
|
|
|
245
251
|
return dispatch(
|
|
246
252
|
field_name=field_name,
|
|
247
253
|
var_data=VarData.from_state(cls, f.name),
|
|
248
|
-
result_var_type=f.outer_type_
|
|
254
|
+
result_var_type=f.outer_type_
|
|
255
|
+
if get_origin(f.outer_type_) is not Field
|
|
256
|
+
else get_args(f.outer_type_)[0],
|
|
249
257
|
)
|
|
250
258
|
|
|
251
259
|
|
|
@@ -419,6 +427,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
419
427
|
if mixin:
|
|
420
428
|
return
|
|
421
429
|
|
|
430
|
+
# Handle locally-defined states for pickling.
|
|
431
|
+
if "<locals>" in cls.__qualname__:
|
|
432
|
+
cls._handle_local_def()
|
|
433
|
+
|
|
422
434
|
# Validate the module name.
|
|
423
435
|
cls._validate_module_name()
|
|
424
436
|
|
|
@@ -465,7 +477,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
465
477
|
new_backend_vars.update(
|
|
466
478
|
{
|
|
467
479
|
name: cls._get_var_default(name, annotation_value)
|
|
468
|
-
for name, annotation_value in
|
|
480
|
+
for name, annotation_value in cls._get_type_hints().items()
|
|
469
481
|
if name not in new_backend_vars
|
|
470
482
|
and types.is_backend_base_variable(name, cls)
|
|
471
483
|
}
|
|
@@ -641,6 +653,39 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
641
653
|
)
|
|
642
654
|
]
|
|
643
655
|
|
|
656
|
+
@classmethod
|
|
657
|
+
def _handle_local_def(cls):
|
|
658
|
+
"""Handle locally-defined states for pickling."""
|
|
659
|
+
known_names = dir(reflex.istate.dynamic)
|
|
660
|
+
proposed_name = cls.__name__
|
|
661
|
+
for ix in range(len(known_names)):
|
|
662
|
+
if proposed_name not in known_names:
|
|
663
|
+
break
|
|
664
|
+
proposed_name = f"{cls.__name__}_{ix}"
|
|
665
|
+
setattr(reflex.istate.dynamic, proposed_name, cls)
|
|
666
|
+
cls.__original_name__ = cls.__name__
|
|
667
|
+
cls.__original_module__ = cls.__module__
|
|
668
|
+
cls.__name__ = cls.__qualname__ = proposed_name
|
|
669
|
+
cls.__module__ = reflex.istate.dynamic.__name__
|
|
670
|
+
|
|
671
|
+
@classmethod
|
|
672
|
+
def _get_type_hints(cls) -> dict[str, Any]:
|
|
673
|
+
"""Get the type hints for this class.
|
|
674
|
+
|
|
675
|
+
If the class is dynamic, evaluate the type hints with the original
|
|
676
|
+
module in the local namespace.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
The type hints dict.
|
|
680
|
+
"""
|
|
681
|
+
original_module = getattr(cls, "__original_module__", None)
|
|
682
|
+
if original_module is not None:
|
|
683
|
+
localns = sys.modules[original_module].__dict__
|
|
684
|
+
else:
|
|
685
|
+
localns = None
|
|
686
|
+
|
|
687
|
+
return get_type_hints(cls, localns=localns)
|
|
688
|
+
|
|
644
689
|
@classmethod
|
|
645
690
|
def _init_var_dependency_dicts(cls):
|
|
646
691
|
"""Initialize the var dependency tracking dicts.
|
|
@@ -691,6 +736,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
691
736
|
parent_state.get_parent_state(),
|
|
692
737
|
)
|
|
693
738
|
|
|
739
|
+
# Reset cached schema value
|
|
740
|
+
cls._to_schema.cache_clear()
|
|
741
|
+
|
|
694
742
|
@classmethod
|
|
695
743
|
def _check_overridden_methods(cls):
|
|
696
744
|
"""Check for shadow methods and raise error if any.
|
|
@@ -1201,7 +1249,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1201
1249
|
name not in self.vars
|
|
1202
1250
|
and name not in self.get_skip_vars()
|
|
1203
1251
|
and not name.startswith("__")
|
|
1204
|
-
and not name.startswith(
|
|
1252
|
+
and not name.startswith(
|
|
1253
|
+
f"_{getattr(type(self), '__original_name__', type(self).__name__)}__"
|
|
1254
|
+
)
|
|
1205
1255
|
):
|
|
1206
1256
|
raise SetUndefinedStateVarError(
|
|
1207
1257
|
f"The state variable '{name}' has not been defined in '{type(self).__name__}'. "
|
|
@@ -1235,6 +1285,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1235
1285
|
default = copy.deepcopy(field.default)
|
|
1236
1286
|
setattr(self, prop_name, default)
|
|
1237
1287
|
|
|
1288
|
+
# Reset the backend vars.
|
|
1289
|
+
for prop_name, value in self.backend_vars.items():
|
|
1290
|
+
setattr(self, prop_name, copy.deepcopy(value))
|
|
1291
|
+
|
|
1238
1292
|
# Recursively reset the substates.
|
|
1239
1293
|
for substate in self.substates.values():
|
|
1240
1294
|
substate.reset()
|
|
@@ -1867,13 +1921,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1867
1921
|
self.dirty_vars.update(self._always_dirty_computed_vars)
|
|
1868
1922
|
self._mark_dirty()
|
|
1869
1923
|
|
|
1870
|
-
def dictify(value: Any):
|
|
1871
|
-
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
1872
|
-
return dataclasses.asdict(value)
|
|
1873
|
-
return value
|
|
1874
|
-
|
|
1875
1924
|
base_vars = {
|
|
1876
|
-
prop_name:
|
|
1925
|
+
prop_name: self.get_value(getattr(self, prop_name))
|
|
1877
1926
|
for prop_name in self.base_vars
|
|
1878
1927
|
}
|
|
1879
1928
|
if initial and include_computed:
|
|
@@ -1945,13 +1994,51 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1945
1994
|
The state dict for serialization.
|
|
1946
1995
|
"""
|
|
1947
1996
|
state = super().__getstate__()
|
|
1948
|
-
# Never serialize parent_state or substates
|
|
1949
1997
|
state["__dict__"] = state["__dict__"].copy()
|
|
1998
|
+
if state["__dict__"].get("parent_state") is not None:
|
|
1999
|
+
# Do not serialize router data in substates (only the root state).
|
|
2000
|
+
state["__dict__"].pop("router", None)
|
|
2001
|
+
state["__dict__"].pop("router_data", None)
|
|
2002
|
+
# Never serialize parent_state or substates.
|
|
1950
2003
|
state["__dict__"]["parent_state"] = None
|
|
1951
2004
|
state["__dict__"]["substates"] = {}
|
|
1952
2005
|
state["__dict__"].pop("_was_touched", None)
|
|
2006
|
+
# Remove all inherited vars.
|
|
2007
|
+
for inherited_var_name in self.inherited_vars:
|
|
2008
|
+
state["__dict__"].pop(inherited_var_name, None)
|
|
1953
2009
|
return state
|
|
1954
2010
|
|
|
2011
|
+
@classmethod
|
|
2012
|
+
@functools.lru_cache()
|
|
2013
|
+
def _to_schema(cls) -> str:
|
|
2014
|
+
"""Convert a state to a schema.
|
|
2015
|
+
|
|
2016
|
+
Returns:
|
|
2017
|
+
The hash of the schema.
|
|
2018
|
+
"""
|
|
2019
|
+
|
|
2020
|
+
def _field_tuple(
|
|
2021
|
+
field_name: str,
|
|
2022
|
+
) -> Tuple[str, str, Any, Union[bool, None], Any]:
|
|
2023
|
+
model_field = cls.__fields__[field_name]
|
|
2024
|
+
return (
|
|
2025
|
+
field_name,
|
|
2026
|
+
model_field.name,
|
|
2027
|
+
_serialize_type(model_field.type_),
|
|
2028
|
+
(
|
|
2029
|
+
model_field.required
|
|
2030
|
+
if isinstance(model_field.required, bool)
|
|
2031
|
+
else None
|
|
2032
|
+
),
|
|
2033
|
+
(model_field.default if is_serializable(model_field.default) else None),
|
|
2034
|
+
)
|
|
2035
|
+
|
|
2036
|
+
return md5(
|
|
2037
|
+
pickle.dumps(
|
|
2038
|
+
list(sorted(_field_tuple(field_name) for field_name in cls.base_vars))
|
|
2039
|
+
)
|
|
2040
|
+
).hexdigest()
|
|
2041
|
+
|
|
1955
2042
|
def _serialize(self) -> bytes:
|
|
1956
2043
|
"""Serialize the state for redis.
|
|
1957
2044
|
|
|
@@ -1959,7 +2046,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1959
2046
|
The serialized state.
|
|
1960
2047
|
"""
|
|
1961
2048
|
try:
|
|
1962
|
-
return pickle.dumps((
|
|
2049
|
+
return pickle.dumps((self._to_schema(), self))
|
|
1963
2050
|
except pickle.PicklingError:
|
|
1964
2051
|
console.warn(
|
|
1965
2052
|
f"Failed to serialize state {self.get_full_name()} due to unpicklable object. "
|
|
@@ -1992,7 +2079,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1992
2079
|
(substate_schema, state) = pickle.load(fp)
|
|
1993
2080
|
else:
|
|
1994
2081
|
raise ValueError("Only one of `data` or `fp` must be provided")
|
|
1995
|
-
if substate_schema !=
|
|
2082
|
+
if substate_schema != state._to_schema():
|
|
1996
2083
|
raise StateSchemaMismatchError()
|
|
1997
2084
|
return state
|
|
1998
2085
|
|
|
@@ -2153,10 +2240,13 @@ class ComponentState(State, mixin=True):
|
|
|
2153
2240
|
cls._per_component_state_instance_count += 1
|
|
2154
2241
|
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
|
|
2155
2242
|
component_state = type(
|
|
2156
|
-
state_cls_name,
|
|
2243
|
+
state_cls_name,
|
|
2244
|
+
(cls, State),
|
|
2245
|
+
{"__module__": reflex.istate.dynamic.__name__},
|
|
2246
|
+
mixin=False,
|
|
2157
2247
|
)
|
|
2158
2248
|
# Save a reference to the dynamic state for pickle/unpickle.
|
|
2159
|
-
|
|
2249
|
+
setattr(reflex.istate.dynamic, state_cls_name, component_state)
|
|
2160
2250
|
component = component_state.get_component(*children, **props)
|
|
2161
2251
|
component.State = component_state
|
|
2162
2252
|
return component
|
|
@@ -2469,20 +2559,32 @@ class StateManager(Base, ABC):
|
|
|
2469
2559
|
Args:
|
|
2470
2560
|
state: The state class to use.
|
|
2471
2561
|
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
)
|
|
2485
|
-
|
|
2562
|
+
Raises:
|
|
2563
|
+
InvalidStateManagerMode: If the state manager mode is invalid.
|
|
2564
|
+
|
|
2565
|
+
Returns:
|
|
2566
|
+
The state manager (either disk, memory or redis).
|
|
2567
|
+
"""
|
|
2568
|
+
config = get_config()
|
|
2569
|
+
if prerequisites.parse_redis_url() is not None:
|
|
2570
|
+
config.state_manager_mode = constants.StateManagerMode.REDIS
|
|
2571
|
+
if config.state_manager_mode == constants.StateManagerMode.MEMORY:
|
|
2572
|
+
return StateManagerMemory(state=state)
|
|
2573
|
+
if config.state_manager_mode == constants.StateManagerMode.DISK:
|
|
2574
|
+
return StateManagerDisk(state=state)
|
|
2575
|
+
if config.state_manager_mode == constants.StateManagerMode.REDIS:
|
|
2576
|
+
redis = prerequisites.get_redis()
|
|
2577
|
+
if redis is not None:
|
|
2578
|
+
# make sure expiration values are obtained only from the config object on creation
|
|
2579
|
+
return StateManagerRedis(
|
|
2580
|
+
state=state,
|
|
2581
|
+
redis=redis,
|
|
2582
|
+
token_expiration=config.redis_token_expiration,
|
|
2583
|
+
lock_expiration=config.redis_lock_expiration,
|
|
2584
|
+
)
|
|
2585
|
+
raise InvalidStateManagerMode(
|
|
2586
|
+
f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
|
|
2587
|
+
)
|
|
2486
2588
|
|
|
2487
2589
|
@abstractmethod
|
|
2488
2590
|
async def get_state(self, token: str) -> BaseState:
|
|
@@ -2627,35 +2729,6 @@ def is_serializable(value: Any) -> bool:
|
|
|
2627
2729
|
return False
|
|
2628
2730
|
|
|
2629
2731
|
|
|
2630
|
-
def state_to_schema(
|
|
2631
|
-
state: BaseState,
|
|
2632
|
-
) -> List[Tuple[str, str, Any, Union[bool, None], Any]]:
|
|
2633
|
-
"""Convert a state to a schema.
|
|
2634
|
-
|
|
2635
|
-
Args:
|
|
2636
|
-
state: The state to convert to a schema.
|
|
2637
|
-
|
|
2638
|
-
Returns:
|
|
2639
|
-
The schema.
|
|
2640
|
-
"""
|
|
2641
|
-
return list(
|
|
2642
|
-
sorted(
|
|
2643
|
-
(
|
|
2644
|
-
field_name,
|
|
2645
|
-
model_field.name,
|
|
2646
|
-
_serialize_type(model_field.type_),
|
|
2647
|
-
(
|
|
2648
|
-
model_field.required
|
|
2649
|
-
if isinstance(model_field.required, bool)
|
|
2650
|
-
else None
|
|
2651
|
-
),
|
|
2652
|
-
(model_field.default if is_serializable(model_field.default) else None),
|
|
2653
|
-
)
|
|
2654
|
-
for field_name, model_field in state.__fields__.items()
|
|
2655
|
-
)
|
|
2656
|
-
)
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
2732
|
def reset_disk_state_manager():
|
|
2660
2733
|
"""Reset the disk state manager."""
|
|
2661
2734
|
states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES
|
|
@@ -2738,34 +2811,24 @@ class StateManagerDisk(StateManager):
|
|
|
2738
2811
|
self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
|
|
2739
2812
|
).absolute()
|
|
2740
2813
|
|
|
2741
|
-
async def load_state(self, token: str
|
|
2814
|
+
async def load_state(self, token: str) -> BaseState | None:
|
|
2742
2815
|
"""Load a state object based on the provided token.
|
|
2743
2816
|
|
|
2744
2817
|
Args:
|
|
2745
2818
|
token: The token used to identify the state object.
|
|
2746
|
-
root_state: The root state object.
|
|
2747
2819
|
|
|
2748
2820
|
Returns:
|
|
2749
|
-
The loaded state object.
|
|
2821
|
+
The loaded state object or None.
|
|
2750
2822
|
"""
|
|
2751
|
-
if token in self.states:
|
|
2752
|
-
return self.states[token]
|
|
2753
|
-
|
|
2754
|
-
client_token, substate_address = _split_substate_key(token)
|
|
2755
|
-
|
|
2756
2823
|
token_path = self.token_path(token)
|
|
2757
2824
|
|
|
2758
2825
|
if token_path.exists():
|
|
2759
2826
|
try:
|
|
2760
2827
|
with token_path.open(mode="rb") as file:
|
|
2761
|
-
|
|
2762
|
-
await self.populate_substates(client_token, substate, root_state)
|
|
2763
|
-
return substate
|
|
2828
|
+
return BaseState._deserialize(fp=file)
|
|
2764
2829
|
except Exception:
|
|
2765
2830
|
pass
|
|
2766
2831
|
|
|
2767
|
-
return root_state.get_substate(substate_address.split(".")[1:])
|
|
2768
|
-
|
|
2769
2832
|
async def populate_substates(
|
|
2770
2833
|
self, client_token: str, state: BaseState, root_state: BaseState
|
|
2771
2834
|
):
|
|
@@ -2779,10 +2842,13 @@ class StateManagerDisk(StateManager):
|
|
|
2779
2842
|
for substate in state.get_substates():
|
|
2780
2843
|
substate_token = _substate_key(client_token, substate)
|
|
2781
2844
|
|
|
2782
|
-
|
|
2845
|
+
instance = await self.load_state(substate_token)
|
|
2846
|
+
if instance is None:
|
|
2847
|
+
instance = await root_state.get_state(substate)
|
|
2848
|
+
state.substates[substate.get_name()] = instance
|
|
2849
|
+
instance.parent_state = state
|
|
2783
2850
|
|
|
2784
|
-
|
|
2785
|
-
substate.parent_state = state
|
|
2851
|
+
await self.populate_substates(client_token, instance, root_state)
|
|
2786
2852
|
|
|
2787
2853
|
@override
|
|
2788
2854
|
async def get_state(
|
|
@@ -2797,15 +2863,24 @@ class StateManagerDisk(StateManager):
|
|
|
2797
2863
|
Returns:
|
|
2798
2864
|
The state for the token.
|
|
2799
2865
|
"""
|
|
2800
|
-
client_token
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2866
|
+
client_token = _split_substate_key(token)[0]
|
|
2867
|
+
root_state = self.states.get(client_token)
|
|
2868
|
+
if root_state is not None:
|
|
2869
|
+
# Retrieved state from memory.
|
|
2870
|
+
return root_state
|
|
2871
|
+
|
|
2872
|
+
# Deserialize root state from disk.
|
|
2873
|
+
root_state = await self.load_state(_substate_key(client_token, self.state))
|
|
2874
|
+
# Create a new root state tree with all substates instantiated.
|
|
2875
|
+
fresh_root_state = self.state(_reflex_internal_init=True)
|
|
2804
2876
|
if root_state is None:
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2877
|
+
root_state = fresh_root_state
|
|
2878
|
+
else:
|
|
2879
|
+
# Ensure all substates exist, even if they were not serialized previously.
|
|
2880
|
+
root_state.substates = fresh_root_state.substates
|
|
2881
|
+
self.states[client_token] = root_state
|
|
2882
|
+
await self.populate_substates(client_token, root_state, root_state)
|
|
2883
|
+
return root_state
|
|
2809
2884
|
|
|
2810
2885
|
async def set_state_for_substate(self, client_token: str, substate: BaseState):
|
|
2811
2886
|
"""Set the state for a substate.
|
|
@@ -2816,13 +2891,13 @@ class StateManagerDisk(StateManager):
|
|
|
2816
2891
|
"""
|
|
2817
2892
|
substate_token = _substate_key(client_token, substate)
|
|
2818
2893
|
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2894
|
+
if substate._get_was_touched():
|
|
2895
|
+
substate._was_touched = False # Reset the touched flag after serializing.
|
|
2896
|
+
pickle_state = substate._serialize()
|
|
2897
|
+
if pickle_state:
|
|
2898
|
+
if not self.states_directory.exists():
|
|
2899
|
+
self.states_directory.mkdir(parents=True, exist_ok=True)
|
|
2900
|
+
self.token_path(substate_token).write_bytes(pickle_state)
|
|
2826
2901
|
|
|
2827
2902
|
for substate_substate in substate.substates.values():
|
|
2828
2903
|
await self.set_state_for_substate(client_token, substate_substate)
|
|
@@ -2902,11 +2977,14 @@ class StateManagerRedis(StateManager):
|
|
|
2902
2977
|
# Only warn about each state class size once.
|
|
2903
2978
|
_warned_about_state_size: ClassVar[Set[str]] = set()
|
|
2904
2979
|
|
|
2905
|
-
async def _get_parent_state(
|
|
2980
|
+
async def _get_parent_state(
|
|
2981
|
+
self, token: str, state: BaseState | None = None
|
|
2982
|
+
) -> BaseState | None:
|
|
2906
2983
|
"""Get the parent state for the state requested in the token.
|
|
2907
2984
|
|
|
2908
2985
|
Args:
|
|
2909
2986
|
token: The token to get the state for (_substate_key).
|
|
2987
|
+
state: The state instance to get parent state for.
|
|
2910
2988
|
|
|
2911
2989
|
Returns:
|
|
2912
2990
|
The parent state for the state requested by the token or None if there is no such parent.
|
|
@@ -2915,11 +2993,15 @@ class StateManagerRedis(StateManager):
|
|
|
2915
2993
|
client_token, state_path = _split_substate_key(token)
|
|
2916
2994
|
parent_state_name = state_path.rpartition(".")[0]
|
|
2917
2995
|
if parent_state_name:
|
|
2996
|
+
cached_substates = None
|
|
2997
|
+
if state is not None:
|
|
2998
|
+
cached_substates = [state]
|
|
2918
2999
|
# Retrieve the parent state to populate event handlers onto this substate.
|
|
2919
3000
|
parent_state = await self.get_state(
|
|
2920
3001
|
token=_substate_key(client_token, parent_state_name),
|
|
2921
3002
|
top_level=False,
|
|
2922
3003
|
get_substates=False,
|
|
3004
|
+
cached_substates=cached_substates,
|
|
2923
3005
|
)
|
|
2924
3006
|
return parent_state
|
|
2925
3007
|
|
|
@@ -2951,6 +3033,8 @@ class StateManagerRedis(StateManager):
|
|
|
2951
3033
|
tasks = {}
|
|
2952
3034
|
# Retrieve the necessary substates from redis.
|
|
2953
3035
|
for substate_cls in fetch_substates:
|
|
3036
|
+
if substate_cls.get_name() in state.substates:
|
|
3037
|
+
continue
|
|
2954
3038
|
substate_name = substate_cls.get_name()
|
|
2955
3039
|
tasks[substate_name] = asyncio.create_task(
|
|
2956
3040
|
self.get_state(
|
|
@@ -2971,6 +3055,7 @@ class StateManagerRedis(StateManager):
|
|
|
2971
3055
|
top_level: bool = True,
|
|
2972
3056
|
get_substates: bool = True,
|
|
2973
3057
|
parent_state: BaseState | None = None,
|
|
3058
|
+
cached_substates: list[BaseState] | None = None,
|
|
2974
3059
|
) -> BaseState:
|
|
2975
3060
|
"""Get the state for a token.
|
|
2976
3061
|
|
|
@@ -2979,6 +3064,7 @@ class StateManagerRedis(StateManager):
|
|
|
2979
3064
|
top_level: If true, return an instance of the top-level state (self.state).
|
|
2980
3065
|
get_substates: If true, also retrieve substates.
|
|
2981
3066
|
parent_state: If provided, use this parent_state instead of getting it from redis.
|
|
3067
|
+
cached_substates: If provided, attach these substates to the state.
|
|
2982
3068
|
|
|
2983
3069
|
Returns:
|
|
2984
3070
|
The state for the token.
|
|
@@ -2996,45 +3082,38 @@ class StateManagerRedis(StateManager):
|
|
|
2996
3082
|
"StateManagerRedis requires token to be specified in the form of {token}_{state_full_name}"
|
|
2997
3083
|
)
|
|
2998
3084
|
|
|
3085
|
+
# The deserialized or newly created (sub)state instance.
|
|
3086
|
+
state = None
|
|
3087
|
+
|
|
2999
3088
|
# Fetch the serialized substate from redis.
|
|
3000
3089
|
redis_state = await self.redis.get(token)
|
|
3001
3090
|
|
|
3002
3091
|
if redis_state is not None:
|
|
3003
3092
|
# Deserialize the substate.
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
# Populate substates if requested.
|
|
3014
|
-
await self._populate_substates(token, state, all_substates=get_substates)
|
|
3015
|
-
|
|
3016
|
-
# To retain compatibility with previous implementation, by default, we return
|
|
3017
|
-
# the top-level state by chasing `parent_state` pointers up the tree.
|
|
3018
|
-
if top_level:
|
|
3019
|
-
return state._get_root_state()
|
|
3020
|
-
return state
|
|
3021
|
-
|
|
3022
|
-
# TODO: dedupe the following logic with the above block
|
|
3023
|
-
# Key didn't exist so we have to create a new instance for this token.
|
|
3093
|
+
with contextlib.suppress(StateSchemaMismatchError):
|
|
3094
|
+
state = BaseState._deserialize(data=redis_state)
|
|
3095
|
+
if state is None:
|
|
3096
|
+
# Key didn't exist or schema mismatch so create a new instance for this token.
|
|
3097
|
+
state = state_cls(
|
|
3098
|
+
init_substates=False,
|
|
3099
|
+
_reflex_internal_init=True,
|
|
3100
|
+
)
|
|
3101
|
+
# Populate parent state if missing and requested.
|
|
3024
3102
|
if parent_state is None:
|
|
3025
|
-
parent_state = await self._get_parent_state(token)
|
|
3026
|
-
# Instantiate the new state class (but don't persist it yet).
|
|
3027
|
-
state = state_cls(
|
|
3028
|
-
parent_state=parent_state,
|
|
3029
|
-
init_substates=False,
|
|
3030
|
-
_reflex_internal_init=True,
|
|
3031
|
-
)
|
|
3103
|
+
parent_state = await self._get_parent_state(token, state)
|
|
3032
3104
|
# Set up Bidirectional linkage between this state and its parent.
|
|
3033
3105
|
if parent_state is not None:
|
|
3034
3106
|
parent_state.substates[state.get_name()] = state
|
|
3035
3107
|
state.parent_state = parent_state
|
|
3036
|
-
#
|
|
3108
|
+
# Avoid fetching substates multiple times.
|
|
3109
|
+
if cached_substates:
|
|
3110
|
+
for substate in cached_substates:
|
|
3111
|
+
state.substates[substate.get_name()] = substate
|
|
3112
|
+
if substate.parent_state is None:
|
|
3113
|
+
substate.parent_state = state
|
|
3114
|
+
# Populate substates if requested.
|
|
3037
3115
|
await self._populate_substates(token, state, all_substates=get_substates)
|
|
3116
|
+
|
|
3038
3117
|
# To retain compatibility with previous implementation, by default, we return
|
|
3039
3118
|
# the top-level state by chasing `parent_state` pointers up the tree.
|
|
3040
3119
|
if top_level:
|
reflex/style.py
CHANGED
|
@@ -10,6 +10,7 @@ from reflex.event import EventChain, EventHandler
|
|
|
10
10
|
from reflex.utils import format
|
|
11
11
|
from reflex.utils.exceptions import ReflexError
|
|
12
12
|
from reflex.utils.imports import ImportVar
|
|
13
|
+
from reflex.utils.types import get_origin
|
|
13
14
|
from reflex.vars import VarData
|
|
14
15
|
from reflex.vars.base import CallableVar, LiteralVar, Var
|
|
15
16
|
from reflex.vars.function import FunctionVar
|
|
@@ -196,6 +197,10 @@ def convert(
|
|
|
196
197
|
isinstance(value, Breakpoints)
|
|
197
198
|
and all(not isinstance(v, dict) for v in value.values())
|
|
198
199
|
)
|
|
200
|
+
or (
|
|
201
|
+
isinstance(value, ObjectVar)
|
|
202
|
+
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
|
|
203
|
+
)
|
|
199
204
|
else (key,)
|
|
200
205
|
)
|
|
201
206
|
|
reflex/testing.py
CHANGED
|
@@ -292,8 +292,6 @@ class AppHarness:
|
|
|
292
292
|
if isinstance(self.app_instance._state_manager, StateManagerRedis):
|
|
293
293
|
# Create our own redis connection for testing.
|
|
294
294
|
self.state_manager = StateManagerRedis.create(self.app_instance.state)
|
|
295
|
-
elif isinstance(self.app_instance._state_manager, StateManagerDisk):
|
|
296
|
-
self.state_manager = StateManagerDisk.create(self.app_instance.state)
|
|
297
295
|
else:
|
|
298
296
|
self.state_manager = self.app_instance._state_manager
|
|
299
297
|
|
|
@@ -396,9 +394,14 @@ class AppHarness:
|
|
|
396
394
|
|
|
397
395
|
def consume_frontend_output():
|
|
398
396
|
while True:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
397
|
+
try:
|
|
398
|
+
line = (
|
|
399
|
+
self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
|
|
400
|
+
)
|
|
401
|
+
# catch I/O operation on closed file.
|
|
402
|
+
except ValueError as e:
|
|
403
|
+
print(e)
|
|
404
|
+
break
|
|
402
405
|
if not line:
|
|
403
406
|
break
|
|
404
407
|
print(line)
|
reflex/utils/compat.py
CHANGED
|
@@ -87,6 +87,4 @@ def sqlmodel_field_has_primary_key(field) -> bool:
|
|
|
87
87
|
return True
|
|
88
88
|
if getattr(field.field_info, "sa_column", None) is None:
|
|
89
89
|
return False
|
|
90
|
-
|
|
91
|
-
return True
|
|
92
|
-
return False
|
|
90
|
+
return bool(getattr(field.field_info.sa_column, "primary_key", None))
|
reflex/utils/exceptions.py
CHANGED
|
@@ -5,6 +5,14 @@ class ReflexError(Exception):
|
|
|
5
5
|
"""Base exception for all Reflex exceptions."""
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class ConfigError(ReflexError):
|
|
9
|
+
"""Custom exception for config related errors."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InvalidStateManagerMode(ReflexError, ValueError):
|
|
13
|
+
"""Raised when an invalid state manager mode is provided."""
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
class ReflexRuntimeError(ReflexError, RuntimeError):
|
|
9
17
|
"""Custom RuntimeError for Reflex."""
|
|
10
18
|
|
reflex/utils/path_ops.py
CHANGED
|
@@ -196,7 +196,7 @@ def get_npm_path() -> str | None:
|
|
|
196
196
|
The path to the npm binary file.
|
|
197
197
|
"""
|
|
198
198
|
npm_path = Path(constants.Node.NPM_PATH)
|
|
199
|
-
if not npm_path.exists():
|
|
199
|
+
if use_system_node() or not npm_path.exists():
|
|
200
200
|
return str(which("npm"))
|
|
201
201
|
return str(npm_path)
|
|
202
202
|
|
|
@@ -218,7 +218,7 @@ def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
|
|
|
218
218
|
|
|
219
219
|
# Read the existing json object from the file.
|
|
220
220
|
json_object = {}
|
|
221
|
-
if fp.stat().st_size
|
|
221
|
+
if fp.stat().st_size:
|
|
222
222
|
with open(fp) as f:
|
|
223
223
|
json_object = json.load(f)
|
|
224
224
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -37,7 +37,7 @@ from reflex.config import Config, get_config
|
|
|
37
37
|
from reflex.utils import console, net, path_ops, processes
|
|
38
38
|
from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
|
|
39
39
|
from reflex.utils.format import format_library_name
|
|
40
|
-
from reflex.utils.registry import
|
|
40
|
+
from reflex.utils.registry import _get_npm_registry
|
|
41
41
|
|
|
42
42
|
CURRENTLY_INSTALLING_NODE = False
|
|
43
43
|
|
|
@@ -620,7 +620,7 @@ def initialize_package_json():
|
|
|
620
620
|
code = _compile_package_json()
|
|
621
621
|
output_path.write_text(code)
|
|
622
622
|
|
|
623
|
-
best_registry =
|
|
623
|
+
best_registry = _get_npm_registry()
|
|
624
624
|
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
|
|
625
625
|
bun_config_path.write_text(
|
|
626
626
|
f"""
|