reflex 0.6.2.post1__py3-none-any.whl → 0.6.3a1__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 +12 -2
- 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 +215 -185
- reflex/components/recharts/cartesian.pyi +623 -832
- reflex/components/recharts/charts.py +57 -54
- 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 +115 -77
- 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 +237 -18
- reflex/experimental/layout.pyi +78 -180
- reflex/istate/dynamic.py +3 -0
- reflex/state.py +195 -118
- 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 +10 -1
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/METADATA +3 -3
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/RECORD +177 -175
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/WHEEL +0 -0
- {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.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,30 @@ 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 config.state_manager_mode == constants.StateManagerMode.DISK:
|
|
2570
|
+
return StateManagerMemory(state=state)
|
|
2571
|
+
if config.state_manager_mode == constants.StateManagerMode.MEMORY:
|
|
2572
|
+
return StateManagerDisk(state=state)
|
|
2573
|
+
if config.state_manager_mode == constants.StateManagerMode.REDIS:
|
|
2574
|
+
redis = prerequisites.get_redis()
|
|
2575
|
+
if redis is not None:
|
|
2576
|
+
# make sure expiration values are obtained only from the config object on creation
|
|
2577
|
+
return StateManagerRedis(
|
|
2578
|
+
state=state,
|
|
2579
|
+
redis=redis,
|
|
2580
|
+
token_expiration=config.redis_token_expiration,
|
|
2581
|
+
lock_expiration=config.redis_lock_expiration,
|
|
2582
|
+
)
|
|
2583
|
+
raise InvalidStateManagerMode(
|
|
2584
|
+
f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
|
|
2585
|
+
)
|
|
2486
2586
|
|
|
2487
2587
|
@abstractmethod
|
|
2488
2588
|
async def get_state(self, token: str) -> BaseState:
|
|
@@ -2627,35 +2727,6 @@ def is_serializable(value: Any) -> bool:
|
|
|
2627
2727
|
return False
|
|
2628
2728
|
|
|
2629
2729
|
|
|
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
2730
|
def reset_disk_state_manager():
|
|
2660
2731
|
"""Reset the disk state manager."""
|
|
2661
2732
|
states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES
|
|
@@ -2738,34 +2809,24 @@ class StateManagerDisk(StateManager):
|
|
|
2738
2809
|
self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
|
|
2739
2810
|
).absolute()
|
|
2740
2811
|
|
|
2741
|
-
async def load_state(self, token: str
|
|
2812
|
+
async def load_state(self, token: str) -> BaseState | None:
|
|
2742
2813
|
"""Load a state object based on the provided token.
|
|
2743
2814
|
|
|
2744
2815
|
Args:
|
|
2745
2816
|
token: The token used to identify the state object.
|
|
2746
|
-
root_state: The root state object.
|
|
2747
2817
|
|
|
2748
2818
|
Returns:
|
|
2749
|
-
The loaded state object.
|
|
2819
|
+
The loaded state object or None.
|
|
2750
2820
|
"""
|
|
2751
|
-
if token in self.states:
|
|
2752
|
-
return self.states[token]
|
|
2753
|
-
|
|
2754
|
-
client_token, substate_address = _split_substate_key(token)
|
|
2755
|
-
|
|
2756
2821
|
token_path = self.token_path(token)
|
|
2757
2822
|
|
|
2758
2823
|
if token_path.exists():
|
|
2759
2824
|
try:
|
|
2760
2825
|
with token_path.open(mode="rb") as file:
|
|
2761
|
-
|
|
2762
|
-
await self.populate_substates(client_token, substate, root_state)
|
|
2763
|
-
return substate
|
|
2826
|
+
return BaseState._deserialize(fp=file)
|
|
2764
2827
|
except Exception:
|
|
2765
2828
|
pass
|
|
2766
2829
|
|
|
2767
|
-
return root_state.get_substate(substate_address.split(".")[1:])
|
|
2768
|
-
|
|
2769
2830
|
async def populate_substates(
|
|
2770
2831
|
self, client_token: str, state: BaseState, root_state: BaseState
|
|
2771
2832
|
):
|
|
@@ -2779,10 +2840,13 @@ class StateManagerDisk(StateManager):
|
|
|
2779
2840
|
for substate in state.get_substates():
|
|
2780
2841
|
substate_token = _substate_key(client_token, substate)
|
|
2781
2842
|
|
|
2782
|
-
|
|
2843
|
+
instance = await self.load_state(substate_token)
|
|
2844
|
+
if instance is None:
|
|
2845
|
+
instance = await root_state.get_state(substate)
|
|
2846
|
+
state.substates[substate.get_name()] = instance
|
|
2847
|
+
instance.parent_state = state
|
|
2783
2848
|
|
|
2784
|
-
|
|
2785
|
-
substate.parent_state = state
|
|
2849
|
+
await self.populate_substates(client_token, instance, root_state)
|
|
2786
2850
|
|
|
2787
2851
|
@override
|
|
2788
2852
|
async def get_state(
|
|
@@ -2797,15 +2861,24 @@ class StateManagerDisk(StateManager):
|
|
|
2797
2861
|
Returns:
|
|
2798
2862
|
The state for the token.
|
|
2799
2863
|
"""
|
|
2800
|
-
client_token
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2864
|
+
client_token = _split_substate_key(token)[0]
|
|
2865
|
+
root_state = self.states.get(client_token)
|
|
2866
|
+
if root_state is not None:
|
|
2867
|
+
# Retrieved state from memory.
|
|
2868
|
+
return root_state
|
|
2869
|
+
|
|
2870
|
+
# Deserialize root state from disk.
|
|
2871
|
+
root_state = await self.load_state(_substate_key(client_token, self.state))
|
|
2872
|
+
# Create a new root state tree with all substates instantiated.
|
|
2873
|
+
fresh_root_state = self.state(_reflex_internal_init=True)
|
|
2804
2874
|
if root_state is None:
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2875
|
+
root_state = fresh_root_state
|
|
2876
|
+
else:
|
|
2877
|
+
# Ensure all substates exist, even if they were not serialized previously.
|
|
2878
|
+
root_state.substates = fresh_root_state.substates
|
|
2879
|
+
self.states[client_token] = root_state
|
|
2880
|
+
await self.populate_substates(client_token, root_state, root_state)
|
|
2881
|
+
return root_state
|
|
2809
2882
|
|
|
2810
2883
|
async def set_state_for_substate(self, client_token: str, substate: BaseState):
|
|
2811
2884
|
"""Set the state for a substate.
|
|
@@ -2816,13 +2889,13 @@ class StateManagerDisk(StateManager):
|
|
|
2816
2889
|
"""
|
|
2817
2890
|
substate_token = _substate_key(client_token, substate)
|
|
2818
2891
|
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2892
|
+
if substate._get_was_touched():
|
|
2893
|
+
substate._was_touched = False # Reset the touched flag after serializing.
|
|
2894
|
+
pickle_state = substate._serialize()
|
|
2895
|
+
if pickle_state:
|
|
2896
|
+
if not self.states_directory.exists():
|
|
2897
|
+
self.states_directory.mkdir(parents=True, exist_ok=True)
|
|
2898
|
+
self.token_path(substate_token).write_bytes(pickle_state)
|
|
2826
2899
|
|
|
2827
2900
|
for substate_substate in substate.substates.values():
|
|
2828
2901
|
await self.set_state_for_substate(client_token, substate_substate)
|
|
@@ -2902,11 +2975,14 @@ class StateManagerRedis(StateManager):
|
|
|
2902
2975
|
# Only warn about each state class size once.
|
|
2903
2976
|
_warned_about_state_size: ClassVar[Set[str]] = set()
|
|
2904
2977
|
|
|
2905
|
-
async def _get_parent_state(
|
|
2978
|
+
async def _get_parent_state(
|
|
2979
|
+
self, token: str, state: BaseState | None = None
|
|
2980
|
+
) -> BaseState | None:
|
|
2906
2981
|
"""Get the parent state for the state requested in the token.
|
|
2907
2982
|
|
|
2908
2983
|
Args:
|
|
2909
2984
|
token: The token to get the state for (_substate_key).
|
|
2985
|
+
state: The state instance to get parent state for.
|
|
2910
2986
|
|
|
2911
2987
|
Returns:
|
|
2912
2988
|
The parent state for the state requested by the token or None if there is no such parent.
|
|
@@ -2915,11 +2991,15 @@ class StateManagerRedis(StateManager):
|
|
|
2915
2991
|
client_token, state_path = _split_substate_key(token)
|
|
2916
2992
|
parent_state_name = state_path.rpartition(".")[0]
|
|
2917
2993
|
if parent_state_name:
|
|
2994
|
+
cached_substates = None
|
|
2995
|
+
if state is not None:
|
|
2996
|
+
cached_substates = [state]
|
|
2918
2997
|
# Retrieve the parent state to populate event handlers onto this substate.
|
|
2919
2998
|
parent_state = await self.get_state(
|
|
2920
2999
|
token=_substate_key(client_token, parent_state_name),
|
|
2921
3000
|
top_level=False,
|
|
2922
3001
|
get_substates=False,
|
|
3002
|
+
cached_substates=cached_substates,
|
|
2923
3003
|
)
|
|
2924
3004
|
return parent_state
|
|
2925
3005
|
|
|
@@ -2951,6 +3031,8 @@ class StateManagerRedis(StateManager):
|
|
|
2951
3031
|
tasks = {}
|
|
2952
3032
|
# Retrieve the necessary substates from redis.
|
|
2953
3033
|
for substate_cls in fetch_substates:
|
|
3034
|
+
if substate_cls.get_name() in state.substates:
|
|
3035
|
+
continue
|
|
2954
3036
|
substate_name = substate_cls.get_name()
|
|
2955
3037
|
tasks[substate_name] = asyncio.create_task(
|
|
2956
3038
|
self.get_state(
|
|
@@ -2971,6 +3053,7 @@ class StateManagerRedis(StateManager):
|
|
|
2971
3053
|
top_level: bool = True,
|
|
2972
3054
|
get_substates: bool = True,
|
|
2973
3055
|
parent_state: BaseState | None = None,
|
|
3056
|
+
cached_substates: list[BaseState] | None = None,
|
|
2974
3057
|
) -> BaseState:
|
|
2975
3058
|
"""Get the state for a token.
|
|
2976
3059
|
|
|
@@ -2979,6 +3062,7 @@ class StateManagerRedis(StateManager):
|
|
|
2979
3062
|
top_level: If true, return an instance of the top-level state (self.state).
|
|
2980
3063
|
get_substates: If true, also retrieve substates.
|
|
2981
3064
|
parent_state: If provided, use this parent_state instead of getting it from redis.
|
|
3065
|
+
cached_substates: If provided, attach these substates to the state.
|
|
2982
3066
|
|
|
2983
3067
|
Returns:
|
|
2984
3068
|
The state for the token.
|
|
@@ -2996,45 +3080,38 @@ class StateManagerRedis(StateManager):
|
|
|
2996
3080
|
"StateManagerRedis requires token to be specified in the form of {token}_{state_full_name}"
|
|
2997
3081
|
)
|
|
2998
3082
|
|
|
3083
|
+
# The deserialized or newly created (sub)state instance.
|
|
3084
|
+
state = None
|
|
3085
|
+
|
|
2999
3086
|
# Fetch the serialized substate from redis.
|
|
3000
3087
|
redis_state = await self.redis.get(token)
|
|
3001
3088
|
|
|
3002
3089
|
if redis_state is not None:
|
|
3003
3090
|
# 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.
|
|
3091
|
+
with contextlib.suppress(StateSchemaMismatchError):
|
|
3092
|
+
state = BaseState._deserialize(data=redis_state)
|
|
3093
|
+
if state is None:
|
|
3094
|
+
# Key didn't exist or schema mismatch so create a new instance for this token.
|
|
3095
|
+
state = state_cls(
|
|
3096
|
+
init_substates=False,
|
|
3097
|
+
_reflex_internal_init=True,
|
|
3098
|
+
)
|
|
3099
|
+
# Populate parent state if missing and requested.
|
|
3024
3100
|
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
|
-
)
|
|
3101
|
+
parent_state = await self._get_parent_state(token, state)
|
|
3032
3102
|
# Set up Bidirectional linkage between this state and its parent.
|
|
3033
3103
|
if parent_state is not None:
|
|
3034
3104
|
parent_state.substates[state.get_name()] = state
|
|
3035
3105
|
state.parent_state = parent_state
|
|
3036
|
-
#
|
|
3106
|
+
# Avoid fetching substates multiple times.
|
|
3107
|
+
if cached_substates:
|
|
3108
|
+
for substate in cached_substates:
|
|
3109
|
+
state.substates[substate.get_name()] = substate
|
|
3110
|
+
if substate.parent_state is None:
|
|
3111
|
+
substate.parent_state = state
|
|
3112
|
+
# Populate substates if requested.
|
|
3037
3113
|
await self._populate_substates(token, state, all_substates=get_substates)
|
|
3114
|
+
|
|
3038
3115
|
# To retain compatibility with previous implementation, by default, we return
|
|
3039
3116
|
# the top-level state by chasing `parent_state` pointers up the tree.
|
|
3040
3117
|
if top_level:
|
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"""
|