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.

Files changed (177) hide show
  1. reflex/__init__.py +3 -2
  2. reflex/__init__.pyi +3 -1
  3. reflex/components/base/app_wrap.pyi +17 -37
  4. reflex/components/base/body.pyi +17 -37
  5. reflex/components/base/document.pyi +77 -177
  6. reflex/components/base/error_boundary.pyi +18 -38
  7. reflex/components/base/fragment.pyi +17 -37
  8. reflex/components/base/head.pyi +32 -72
  9. reflex/components/base/link.pyi +32 -72
  10. reflex/components/base/meta.pyi +62 -142
  11. reflex/components/base/script.py +4 -4
  12. reflex/components/base/script.pyi +20 -40
  13. reflex/components/component.py +12 -2
  14. reflex/components/core/banner.pyi +77 -177
  15. reflex/components/core/client_side_routing.pyi +32 -72
  16. reflex/components/core/clipboard.py +3 -3
  17. reflex/components/core/clipboard.pyi +18 -38
  18. reflex/components/core/debounce.py +2 -2
  19. reflex/components/core/debounce.pyi +18 -38
  20. reflex/components/core/html.pyi +17 -37
  21. reflex/components/core/upload.py +4 -5
  22. reflex/components/core/upload.pyi +65 -145
  23. reflex/components/datadisplay/code.pyi +32 -72
  24. reflex/components/datadisplay/dataeditor.py +13 -13
  25. reflex/components/datadisplay/dataeditor.pyi +33 -83
  26. reflex/components/dynamic.py +6 -7
  27. reflex/components/el/__init__.pyi +1 -0
  28. reflex/components/el/element.pyi +17 -37
  29. reflex/components/el/elements/base.pyi +17 -37
  30. reflex/components/el/elements/forms.py +29 -14
  31. reflex/components/el/elements/forms.pyi +222 -504
  32. reflex/components/el/elements/inline.pyi +422 -982
  33. reflex/components/el/elements/media.pyi +377 -877
  34. reflex/components/el/elements/metadata.pyi +92 -212
  35. reflex/components/el/elements/other.pyi +107 -247
  36. reflex/components/el/elements/scripts.pyi +47 -107
  37. reflex/components/el/elements/sectioning.pyi +227 -527
  38. reflex/components/el/elements/tables.pyi +152 -352
  39. reflex/components/el/elements/typography.pyi +227 -527
  40. reflex/components/gridjs/datatable.py +2 -2
  41. reflex/components/gridjs/datatable.pyi +32 -72
  42. reflex/components/lucide/icon.pyi +32 -72
  43. reflex/components/markdown/markdown.pyi +16 -36
  44. reflex/components/moment/moment.py +2 -2
  45. reflex/components/moment/moment.pyi +18 -38
  46. reflex/components/next/base.pyi +17 -37
  47. reflex/components/next/image.py +3 -3
  48. reflex/components/next/image.pyi +19 -39
  49. reflex/components/next/link.pyi +17 -37
  50. reflex/components/next/video.pyi +17 -37
  51. reflex/components/plotly/plotly.py +1 -1
  52. reflex/components/plotly/plotly.pyi +35 -87
  53. reflex/components/radix/primitives/accordion.py +14 -2
  54. reflex/components/radix/primitives/accordion.pyi +110 -250
  55. reflex/components/radix/primitives/base.pyi +32 -72
  56. reflex/components/radix/primitives/drawer.py +30 -12
  57. reflex/components/radix/primitives/drawer.pyi +159 -373
  58. reflex/components/radix/primitives/form.py +3 -3
  59. reflex/components/radix/primitives/form.pyi +158 -364
  60. reflex/components/radix/primitives/progress.pyi +77 -177
  61. reflex/components/radix/primitives/slider.py +17 -3
  62. reflex/components/radix/primitives/slider.pyi +81 -183
  63. reflex/components/radix/themes/base.pyi +107 -247
  64. reflex/components/radix/themes/color_mode.pyi +48 -117
  65. reflex/components/radix/themes/components/alert_dialog.py +5 -5
  66. reflex/components/radix/themes/components/alert_dialog.pyi +111 -259
  67. reflex/components/radix/themes/components/aspect_ratio.pyi +17 -37
  68. reflex/components/radix/themes/components/avatar.pyi +17 -37
  69. reflex/components/radix/themes/components/badge.pyi +17 -37
  70. reflex/components/radix/themes/components/button.pyi +17 -37
  71. reflex/components/radix/themes/components/callout.pyi +77 -177
  72. reflex/components/radix/themes/components/card.pyi +17 -37
  73. reflex/components/radix/themes/components/checkbox.py +3 -3
  74. reflex/components/radix/themes/components/checkbox.pyi +50 -110
  75. reflex/components/radix/themes/components/checkbox_cards.pyi +32 -72
  76. reflex/components/radix/themes/components/checkbox_group.pyi +32 -72
  77. reflex/components/radix/themes/components/context_menu.py +11 -11
  78. reflex/components/radix/themes/components/context_menu.pyi +132 -312
  79. reflex/components/radix/themes/components/data_list.pyi +62 -142
  80. reflex/components/radix/themes/components/dialog.py +7 -7
  81. reflex/components/radix/themes/components/dialog.pyi +114 -268
  82. reflex/components/radix/themes/components/dropdown_menu.py +13 -13
  83. reflex/components/radix/themes/components/dropdown_menu.pyi +134 -316
  84. reflex/components/radix/themes/components/hover_card.py +2 -2
  85. reflex/components/radix/themes/components/hover_card.pyi +64 -148
  86. reflex/components/radix/themes/components/icon_button.pyi +17 -37
  87. reflex/components/radix/themes/components/inset.pyi +17 -37
  88. reflex/components/radix/themes/components/popover.py +8 -8
  89. reflex/components/radix/themes/components/popover.pyi +69 -163
  90. reflex/components/radix/themes/components/progress.pyi +17 -37
  91. reflex/components/radix/themes/components/radio.pyi +17 -37
  92. reflex/components/radix/themes/components/radio_cards.py +2 -2
  93. reflex/components/radix/themes/components/radio_cards.pyi +33 -75
  94. reflex/components/radix/themes/components/radio_group.py +4 -4
  95. reflex/components/radix/themes/components/radio_group.pyi +63 -143
  96. reflex/components/radix/themes/components/scroll_area.pyi +17 -37
  97. reflex/components/radix/themes/components/segmented_control.py +14 -2
  98. reflex/components/radix/themes/components/segmented_control.pyi +35 -73
  99. reflex/components/radix/themes/components/select.py +6 -5
  100. reflex/components/radix/themes/components/select.pyi +146 -338
  101. reflex/components/radix/themes/components/separator.pyi +17 -37
  102. reflex/components/radix/themes/components/skeleton.pyi +17 -37
  103. reflex/components/radix/themes/components/slider.py +19 -3
  104. reflex/components/radix/themes/components/slider.pyi +23 -41
  105. reflex/components/radix/themes/components/spinner.pyi +17 -37
  106. reflex/components/radix/themes/components/switch.py +2 -2
  107. reflex/components/radix/themes/components/switch.pyi +18 -38
  108. reflex/components/radix/themes/components/table.pyi +107 -247
  109. reflex/components/radix/themes/components/tabs.py +2 -2
  110. reflex/components/radix/themes/components/tabs.pyi +79 -179
  111. reflex/components/radix/themes/components/text_area.py +0 -16
  112. reflex/components/radix/themes/components/text_area.pyi +20 -42
  113. reflex/components/radix/themes/components/text_field.py +6 -6
  114. reflex/components/radix/themes/components/text_field.pyi +53 -117
  115. reflex/components/radix/themes/components/tooltip.py +4 -4
  116. reflex/components/radix/themes/components/tooltip.pyi +20 -46
  117. reflex/components/radix/themes/layout/base.pyi +17 -37
  118. reflex/components/radix/themes/layout/box.pyi +17 -37
  119. reflex/components/radix/themes/layout/center.pyi +17 -37
  120. reflex/components/radix/themes/layout/container.pyi +17 -37
  121. reflex/components/radix/themes/layout/flex.pyi +17 -37
  122. reflex/components/radix/themes/layout/grid.pyi +17 -37
  123. reflex/components/radix/themes/layout/list.pyi +77 -177
  124. reflex/components/radix/themes/layout/section.pyi +17 -37
  125. reflex/components/radix/themes/layout/spacer.pyi +17 -37
  126. reflex/components/radix/themes/layout/stack.pyi +47 -107
  127. reflex/components/radix/themes/typography/blockquote.pyi +17 -37
  128. reflex/components/radix/themes/typography/code.pyi +17 -37
  129. reflex/components/radix/themes/typography/heading.pyi +17 -37
  130. reflex/components/radix/themes/typography/link.pyi +17 -37
  131. reflex/components/radix/themes/typography/text.pyi +107 -247
  132. reflex/components/react_player/audio.pyi +33 -69
  133. reflex/components/react_player/react_player.py +17 -17
  134. reflex/components/react_player/react_player.pyi +33 -69
  135. reflex/components/react_player/video.pyi +33 -69
  136. reflex/components/recharts/cartesian.py +215 -185
  137. reflex/components/recharts/cartesian.pyi +623 -832
  138. reflex/components/recharts/charts.py +57 -54
  139. reflex/components/recharts/charts.pyi +213 -433
  140. reflex/components/recharts/general.py +27 -21
  141. reflex/components/recharts/general.pyi +94 -189
  142. reflex/components/recharts/polar.py +115 -77
  143. reflex/components/recharts/polar.pyi +219 -229
  144. reflex/components/recharts/recharts.py +5 -1
  145. reflex/components/recharts/recharts.pyi +37 -73
  146. reflex/components/sonner/toast.py +1 -1
  147. reflex/components/sonner/toast.pyi +17 -37
  148. reflex/components/suneditor/editor.pyi +26 -52
  149. reflex/components/tags/iter_tag.py +2 -2
  150. reflex/config.py +16 -0
  151. reflex/constants/__init__.py +2 -0
  152. reflex/constants/compiler.py +25 -0
  153. reflex/constants/installer.py +17 -16
  154. reflex/constants/state.py +11 -0
  155. reflex/constants/style.py +1 -1
  156. reflex/event.py +237 -18
  157. reflex/experimental/layout.pyi +78 -180
  158. reflex/istate/dynamic.py +3 -0
  159. reflex/state.py +195 -118
  160. reflex/testing.py +8 -5
  161. reflex/utils/compat.py +1 -3
  162. reflex/utils/exceptions.py +8 -0
  163. reflex/utils/path_ops.py +2 -2
  164. reflex/utils/prerequisites.py +2 -2
  165. reflex/utils/pyi_generator.py +44 -4
  166. reflex/utils/registry.py +17 -3
  167. reflex/utils/telemetry.py +1 -3
  168. reflex/utils/types.py +60 -16
  169. reflex/vars/__init__.py +2 -0
  170. reflex/vars/base.py +127 -72
  171. reflex/vars/object.py +5 -1
  172. reflex/vars/sequence.py +10 -1
  173. {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/METADATA +3 -3
  174. {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/RECORD +177 -175
  175. {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/LICENSE +0 -0
  176. {reflex-0.6.2.post1.dist-info → reflex-0.6.3a1.dist-info}/WHEEL +0 -0
  177. {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 get_type_hints(cls).items()
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(f"_{type(self).__name__}__")
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: dictify(self.get_value(getattr(self, 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((state_to_schema(self), self))
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 != state_to_schema(state):
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, (cls, State), {"__module__": __name__}, mixin=False
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
- globals()[state_cls_name] = component_state
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
- Returns:
2473
- The state manager (either memory or redis).
2474
- """
2475
- redis = prerequisites.get_redis()
2476
- if redis is not None:
2477
- # make sure expiration values are obtained only from the config object on creation
2478
- config = get_config()
2479
- return StateManagerRedis(
2480
- state=state,
2481
- redis=redis,
2482
- token_expiration=config.redis_token_expiration,
2483
- lock_expiration=config.redis_lock_expiration,
2484
- )
2485
- return StateManagerDisk(state=state)
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, root_state: BaseState) -> BaseState:
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
- substate = BaseState._deserialize(fp=file)
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
- substate = await self.load_state(substate_token, root_state)
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
- state.substates[substate.get_name()] = substate
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, substate_address = _split_substate_key(token)
2801
-
2802
- root_state_token = _substate_key(client_token, substate_address.split(".")[0])
2803
- root_state = self.states.get(root_state_token)
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
- # Create a new root state which will be persisted in the next set_state call.
2806
- root_state = self.state(_reflex_internal_init=True)
2807
-
2808
- return await self.load_state(root_state_token, root_state)
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
- self.states[substate_token] = substate
2820
-
2821
- state_dilled = substate._serialize()
2822
- if state_dilled:
2823
- if not self.states_directory.exists():
2824
- self.states_directory.mkdir(parents=True, exist_ok=True)
2825
- self.token_path(substate_token).write_bytes(state_dilled)
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(self, token: str) -> BaseState | None:
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
- state = BaseState._deserialize(data=redis_state)
3005
-
3006
- # Populate parent state if missing and requested.
3007
- if parent_state is None:
3008
- parent_state = await self._get_parent_state(token)
3009
- # Set up Bidirectional linkage between this state and its parent.
3010
- if parent_state is not None:
3011
- parent_state.substates[state.get_name()] = state
3012
- state.parent_state = parent_state
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
- # Populate substates for the newly created state.
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
- line = (
400
- self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
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
- if getattr(field.field_info.sa_column, "primary_key", None) is True:
91
- return True
92
- return False
90
+ return bool(getattr(field.field_info.sa_column, "primary_key", None))
@@ -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 == 0:
221
+ if fp.stat().st_size:
222
222
  with open(fp) as f:
223
223
  json_object = json.load(f)
224
224
 
@@ -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 _get_best_registry
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 = _get_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"""