reflex 0.6.0a1__py3-none-any.whl → 0.6.0a3__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 (252) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +2 -2
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +1 -1
  3. reflex/.templates/jinja/web/pages/utils.js.jinja2 +2 -2
  4. reflex/__init__.py +8 -2
  5. reflex/__init__.pyi +2 -1
  6. reflex/app.py +10 -4
  7. reflex/base.py +1 -1
  8. reflex/compiler/compiler.py +2 -2
  9. reflex/compiler/utils.py +3 -3
  10. reflex/components/base/app_wrap.py +2 -2
  11. reflex/components/base/app_wrap.pyi +17 -27
  12. reflex/components/base/bare.py +4 -5
  13. reflex/components/base/body.pyi +17 -27
  14. reflex/components/base/document.pyi +81 -131
  15. reflex/components/base/error_boundary.py +6 -7
  16. reflex/components/base/error_boundary.pyi +20 -33
  17. reflex/components/base/fragment.pyi +17 -27
  18. reflex/components/base/head.pyi +33 -53
  19. reflex/components/base/link.py +1 -1
  20. reflex/components/base/link.pyi +33 -54
  21. reflex/components/base/meta.pyi +65 -105
  22. reflex/components/base/script.py +1 -2
  23. reflex/components/base/script.pyi +21 -38
  24. reflex/components/component.py +45 -47
  25. reflex/components/core/banner.py +23 -27
  26. reflex/components/core/banner.pyi +134 -171
  27. reflex/components/core/breakpoints.py +3 -1
  28. reflex/components/core/client_side_routing.py +2 -3
  29. reflex/components/core/client_side_routing.pyi +33 -54
  30. reflex/components/core/clipboard.py +2 -1
  31. reflex/components/core/clipboard.pyi +20 -33
  32. reflex/components/core/cond.py +5 -5
  33. reflex/components/core/debounce.py +5 -5
  34. reflex/components/core/debounce.pyi +20 -33
  35. reflex/components/core/foreach.py +3 -4
  36. reflex/components/core/html.py +1 -1
  37. reflex/components/core/html.pyi +35 -46
  38. reflex/components/core/match.py +17 -17
  39. reflex/components/core/upload.py +17 -23
  40. reflex/components/core/upload.pyi +78 -124
  41. reflex/components/datadisplay/code.py +9 -10
  42. reflex/components/datadisplay/code.pyi +302 -412
  43. reflex/components/datadisplay/dataeditor.py +8 -10
  44. reflex/components/datadisplay/dataeditor.pyi +40 -53
  45. reflex/components/el/element.pyi +17 -27
  46. reflex/components/el/elements/base.py +1 -1
  47. reflex/components/el/elements/base.pyi +34 -45
  48. reflex/components/el/elements/forms.py +16 -16
  49. reflex/components/el/elements/forms.pyi +554 -707
  50. reflex/components/el/elements/inline.py +1 -1
  51. reflex/components/el/elements/inline.pyi +937 -1218
  52. reflex/components/el/elements/media.py +1 -1
  53. reflex/components/el/elements/media.pyi +786 -997
  54. reflex/components/el/elements/metadata.py +3 -6
  55. reflex/components/el/elements/metadata.pyi +181 -242
  56. reflex/components/el/elements/other.py +1 -1
  57. reflex/components/el/elements/other.pyi +235 -306
  58. reflex/components/el/elements/scripts.py +1 -1
  59. reflex/components/el/elements/scripts.pyi +109 -140
  60. reflex/components/el/elements/sectioning.py +0 -2
  61. reflex/components/el/elements/sectioning.pyi +496 -647
  62. reflex/components/el/elements/tables.py +1 -1
  63. reflex/components/el/elements/tables.pyi +351 -452
  64. reflex/components/el/elements/typography.py +1 -1
  65. reflex/components/el/elements/typography.pyi +506 -657
  66. reflex/components/gridjs/datatable.py +6 -9
  67. reflex/components/gridjs/datatable.pyi +35 -56
  68. reflex/components/lucide/icon.py +1 -1
  69. reflex/components/lucide/icon.pyi +33 -54
  70. reflex/components/markdown/markdown.py +26 -31
  71. reflex/components/markdown/markdown.pyi +27 -37
  72. reflex/components/moment/moment.py +13 -12
  73. reflex/components/moment/moment.pyi +23 -35
  74. reflex/components/next/base.pyi +17 -27
  75. reflex/components/next/image.py +1 -1
  76. reflex/components/next/image.pyi +22 -37
  77. reflex/components/next/link.py +1 -1
  78. reflex/components/next/link.pyi +17 -28
  79. reflex/components/next/video.py +1 -1
  80. reflex/components/next/video.pyi +17 -28
  81. reflex/components/plotly/plotly.py +12 -13
  82. reflex/components/plotly/plotly.pyi +39 -54
  83. reflex/components/props.py +1 -1
  84. reflex/components/radix/__init__.pyi +1 -0
  85. reflex/components/radix/primitives/__init__.pyi +1 -0
  86. reflex/components/radix/primitives/accordion.py +4 -4
  87. reflex/components/radix/primitives/accordion.pyi +424 -495
  88. reflex/components/radix/primitives/base.py +1 -1
  89. reflex/components/radix/primitives/base.pyi +33 -54
  90. reflex/components/radix/primitives/drawer.py +1 -1
  91. reflex/components/radix/primitives/drawer.pyi +172 -273
  92. reflex/components/radix/primitives/form.py +1 -1
  93. reflex/components/radix/primitives/form.pyi +257 -364
  94. reflex/components/radix/primitives/progress.py +1 -1
  95. reflex/components/radix/primitives/progress.pyi +231 -282
  96. reflex/components/radix/primitives/slider.py +1 -1
  97. reflex/components/radix/primitives/slider.pyi +87 -138
  98. reflex/components/radix/themes/base.py +3 -24
  99. reflex/components/radix/themes/base.pyi +178 -250
  100. reflex/components/radix/themes/color_mode.py +5 -5
  101. reflex/components/radix/themes/color_mode.pyi +187 -220
  102. reflex/components/radix/themes/components/alert_dialog.py +1 -1
  103. reflex/components/radix/themes/components/alert_dialog.pyi +136 -207
  104. reflex/components/radix/themes/components/aspect_ratio.py +1 -1
  105. reflex/components/radix/themes/components/aspect_ratio.pyi +17 -28
  106. reflex/components/radix/themes/components/avatar.py +1 -1
  107. reflex/components/radix/themes/components/avatar.pyi +70 -81
  108. reflex/components/radix/themes/components/badge.py +1 -1
  109. reflex/components/radix/themes/components/badge.pyi +88 -99
  110. reflex/components/radix/themes/components/button.py +1 -1
  111. reflex/components/radix/themes/components/button.pyi +98 -109
  112. reflex/components/radix/themes/components/callout.py +1 -1
  113. reflex/components/radix/themes/components/callout.pyi +322 -373
  114. reflex/components/radix/themes/components/card.py +1 -1
  115. reflex/components/radix/themes/components/card.pyi +38 -49
  116. reflex/components/radix/themes/components/checkbox.py +1 -2
  117. reflex/components/radix/themes/components/checkbox.pyi +208 -245
  118. reflex/components/radix/themes/components/checkbox_cards.py +1 -1
  119. reflex/components/radix/themes/components/checkbox_cards.pyi +94 -115
  120. reflex/components/radix/themes/components/checkbox_group.py +1 -1
  121. reflex/components/radix/themes/components/checkbox_group.pyi +86 -107
  122. reflex/components/radix/themes/components/context_menu.py +1 -1
  123. reflex/components/radix/themes/components/context_menu.pyi +238 -319
  124. reflex/components/radix/themes/components/data_list.py +1 -1
  125. reflex/components/radix/themes/components/data_list.pyi +130 -171
  126. reflex/components/radix/themes/components/dialog.py +1 -1
  127. reflex/components/radix/themes/components/dialog.pyi +139 -210
  128. reflex/components/radix/themes/components/dropdown_menu.py +1 -1
  129. reflex/components/radix/themes/components/dropdown_menu.pyi +249 -332
  130. reflex/components/radix/themes/components/hover_card.py +1 -1
  131. reflex/components/radix/themes/components/hover_card.pyi +90 -131
  132. reflex/components/radix/themes/components/icon_button.py +2 -3
  133. reflex/components/radix/themes/components/icon_button.pyi +98 -109
  134. reflex/components/radix/themes/components/inset.py +1 -1
  135. reflex/components/radix/themes/components/inset.pyi +47 -58
  136. reflex/components/radix/themes/components/popover.py +1 -1
  137. reflex/components/radix/themes/components/popover.pyi +95 -136
  138. reflex/components/radix/themes/components/progress.py +1 -1
  139. reflex/components/radix/themes/components/progress.pyi +71 -82
  140. reflex/components/radix/themes/components/radio.py +1 -1
  141. reflex/components/radix/themes/components/radio.pyi +69 -80
  142. reflex/components/radix/themes/components/radio_cards.py +1 -1
  143. reflex/components/radix/themes/components/radio_cards.pyi +98 -119
  144. reflex/components/radix/themes/components/radio_group.py +8 -11
  145. reflex/components/radix/themes/components/radio_group.pyi +228 -271
  146. reflex/components/radix/themes/components/scroll_area.py +1 -1
  147. reflex/components/radix/themes/components/scroll_area.pyi +21 -32
  148. reflex/components/radix/themes/components/segmented_control.py +1 -1
  149. reflex/components/radix/themes/components/segmented_control.pyi +90 -113
  150. reflex/components/radix/themes/components/select.py +2 -3
  151. reflex/components/radix/themes/components/select.pyi +374 -471
  152. reflex/components/radix/themes/components/separator.py +1 -2
  153. reflex/components/radix/themes/components/separator.pyi +69 -80
  154. reflex/components/radix/themes/components/skeleton.py +1 -1
  155. reflex/components/radix/themes/components/skeleton.pyi +23 -34
  156. reflex/components/radix/themes/components/slider.py +2 -3
  157. reflex/components/radix/themes/components/slider.pyi +75 -88
  158. reflex/components/radix/themes/components/spinner.py +1 -1
  159. reflex/components/radix/themes/components/spinner.pyi +19 -30
  160. reflex/components/radix/themes/components/switch.py +1 -1
  161. reflex/components/radix/themes/components/switch.pyi +71 -84
  162. reflex/components/radix/themes/components/table.py +1 -1
  163. reflex/components/radix/themes/components/table.pyi +261 -332
  164. reflex/components/radix/themes/components/tabs.py +1 -1
  165. reflex/components/radix/themes/components/tabs.pyi +139 -194
  166. reflex/components/radix/themes/components/text_area.py +1 -1
  167. reflex/components/radix/themes/components/text_area.pyi +96 -111
  168. reflex/components/radix/themes/components/text_field.py +1 -1
  169. reflex/components/radix/themes/components/text_field.pyi +247 -286
  170. reflex/components/radix/themes/components/tooltip.py +1 -1
  171. reflex/components/radix/themes/components/tooltip.pyi +26 -37
  172. reflex/components/radix/themes/layout/__init__.pyi +1 -0
  173. reflex/components/radix/themes/layout/base.py +1 -1
  174. reflex/components/radix/themes/layout/base.pyi +56 -67
  175. reflex/components/radix/themes/layout/box.pyi +34 -45
  176. reflex/components/radix/themes/layout/center.pyi +56 -67
  177. reflex/components/radix/themes/layout/container.py +1 -2
  178. reflex/components/radix/themes/layout/container.pyi +36 -47
  179. reflex/components/radix/themes/layout/flex.py +1 -1
  180. reflex/components/radix/themes/layout/flex.pyi +56 -67
  181. reflex/components/radix/themes/layout/grid.py +1 -1
  182. reflex/components/radix/themes/layout/grid.pyi +64 -75
  183. reflex/components/radix/themes/layout/list.py +5 -6
  184. reflex/components/radix/themes/layout/list.pyi +193 -244
  185. reflex/components/radix/themes/layout/section.py +1 -2
  186. reflex/components/radix/themes/layout/section.pyi +36 -47
  187. reflex/components/radix/themes/layout/spacer.pyi +56 -67
  188. reflex/components/radix/themes/layout/stack.py +1 -1
  189. reflex/components/radix/themes/layout/stack.pyi +128 -159
  190. reflex/components/radix/themes/typography/blockquote.py +1 -1
  191. reflex/components/radix/themes/typography/blockquote.pyi +89 -100
  192. reflex/components/radix/themes/typography/code.py +1 -1
  193. reflex/components/radix/themes/typography/code.pyi +90 -101
  194. reflex/components/radix/themes/typography/heading.py +1 -1
  195. reflex/components/radix/themes/typography/heading.pyi +96 -107
  196. reflex/components/radix/themes/typography/link.py +1 -1
  197. reflex/components/radix/themes/typography/link.pyi +102 -113
  198. reflex/components/radix/themes/typography/text.py +1 -1
  199. reflex/components/radix/themes/typography/text.pyi +501 -572
  200. reflex/components/react_player/audio.pyi +33 -60
  201. reflex/components/react_player/react_player.py +1 -1
  202. reflex/components/react_player/react_player.pyi +33 -60
  203. reflex/components/react_player/video.pyi +33 -60
  204. reflex/components/recharts/cartesian.py +2 -3
  205. reflex/components/recharts/cartesian.pyi +678 -861
  206. reflex/components/recharts/charts.py +4 -5
  207. reflex/components/recharts/charts.pyi +252 -357
  208. reflex/components/recharts/general.py +1 -2
  209. reflex/components/recharts/general.pyi +180 -231
  210. reflex/components/recharts/polar.py +4 -5
  211. reflex/components/recharts/polar.pyi +144 -181
  212. reflex/components/recharts/recharts.pyi +33 -53
  213. reflex/components/sonner/toast.py +16 -17
  214. reflex/components/sonner/toast.pyi +36 -47
  215. reflex/components/suneditor/editor.py +2 -3
  216. reflex/components/suneditor/editor.pyi +55 -78
  217. reflex/components/tags/cond_tag.py +6 -4
  218. reflex/components/tags/iter_tag.py +28 -16
  219. reflex/components/tags/match_tag.py +6 -4
  220. reflex/components/tags/tag.py +40 -23
  221. reflex/custom_components/custom_components.py +3 -1
  222. reflex/event.py +115 -67
  223. reflex/experimental/client_state.py +18 -18
  224. reflex/experimental/hooks.py +16 -16
  225. reflex/experimental/layout.py +5 -5
  226. reflex/experimental/layout.pyi +136 -187
  227. reflex/middleware/hydrate_middleware.py +2 -0
  228. reflex/middleware/middleware.py +3 -3
  229. reflex/state.py +149 -82
  230. reflex/style.py +21 -22
  231. reflex/utils/exceptions.py +20 -0
  232. reflex/utils/format.py +54 -34
  233. reflex/utils/imports.py +16 -73
  234. reflex/utils/prerequisites.py +15 -8
  235. reflex/utils/pyi_generator.py +13 -8
  236. reflex/utils/serializers.py +12 -22
  237. reflex/utils/telemetry.py +3 -2
  238. reflex/utils/types.py +11 -6
  239. reflex/{ivars → vars}/__init__.py +6 -2
  240. reflex/{ivars → vars}/base.py +599 -216
  241. reflex/{ivars → vars}/function.py +15 -19
  242. reflex/{ivars → vars}/number.py +41 -20
  243. reflex/{ivars → vars}/object.py +28 -30
  244. reflex/{ivars → vars}/sequence.py +53 -42
  245. {reflex-0.6.0a1.dist-info → reflex-0.6.0a3.dist-info}/METADATA +4 -6
  246. reflex-0.6.0a3.dist-info/RECORD +382 -0
  247. reflex/.templates/web/components/reflex/chakra_color_mode_provider.js +0 -36
  248. reflex/vars.py +0 -501
  249. reflex-0.6.0a1.dist-info/RECORD +0 -384
  250. {reflex-0.6.0a1.dist-info → reflex-0.6.0a3.dist-info}/LICENSE +0 -0
  251. {reflex-0.6.0a1.dist-info → reflex-0.6.0a3.dist-info}/WHEEL +0 -0
  252. {reflex-0.6.0a1.dist-info → reflex-0.6.0a3.dist-info}/entry_points.txt +0 -0
@@ -9,7 +9,11 @@ import dis
9
9
  import functools
10
10
  import inspect
11
11
  import json
12
+ import random
13
+ import re
14
+ import string
12
15
  import sys
16
+ import warnings
13
17
  from types import CodeType, FunctionType
14
18
  from typing import (
15
19
  TYPE_CHECKING,
@@ -17,6 +21,7 @@ from typing import (
17
21
  Callable,
18
22
  Dict,
19
23
  Generic,
24
+ Iterable,
20
25
  List,
21
26
  Literal,
22
27
  NoReturn,
@@ -43,15 +48,14 @@ from reflex.utils.exceptions import (
43
48
  VarValueError,
44
49
  )
45
50
  from reflex.utils.format import format_state_name
46
- from reflex.utils.types import GenericType, Self, get_origin
47
- from reflex.vars import (
48
- REPLACED_NAMES,
49
- Var,
50
- VarData,
51
- _decode_var_immutable,
52
- _extract_var_data,
53
- _global_vars,
51
+ from reflex.utils.imports import (
52
+ ImmutableParsedImportDict,
53
+ ImportDict,
54
+ ImportVar,
55
+ ParsedImportDict,
56
+ parse_imports,
54
57
  )
58
+ from reflex.utils.types import GenericType, Self, get_origin
55
59
 
56
60
  if TYPE_CHECKING:
57
61
  from reflex.state import BaseState
@@ -67,19 +71,20 @@ if TYPE_CHECKING:
67
71
  from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
68
72
 
69
73
 
70
- VAR_TYPE = TypeVar("VAR_TYPE")
74
+ VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
75
+
76
+ warnings.filterwarnings("ignore", message="fields may not start with an underscore")
71
77
 
72
78
 
73
79
  @dataclasses.dataclass(
74
80
  eq=False,
75
81
  frozen=True,
76
- **{"slots": True} if sys.version_info >= (3, 10) else {},
77
82
  )
78
- class ImmutableVar(Var, Generic[VAR_TYPE]):
83
+ class Var(Generic[VAR_TYPE]):
79
84
  """Base class for immutable vars."""
80
85
 
81
86
  # The name of the var.
82
- _var_name: str = dataclasses.field()
87
+ _js_expr: str = dataclasses.field()
83
88
 
84
89
  # The type of the var.
85
90
  _var_type: types.GenericType = dataclasses.field(default=Any)
@@ -93,7 +98,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
93
98
  Returns:
94
99
  The name of the var.
95
100
  """
96
- return self._var_name
101
+ return self._js_expr
97
102
 
98
103
  @property
99
104
  def _var_is_local(self) -> bool:
@@ -104,6 +109,26 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
104
109
  """
105
110
  return False
106
111
 
112
+ @property
113
+ @deprecated("Use `_js_expr` instead.")
114
+ def _var_name(self) -> str:
115
+ """The name of the var.
116
+
117
+ Returns:
118
+ The name of the var.
119
+ """
120
+ return self._js_expr
121
+
122
+ @property
123
+ @deprecated("Use `_js_expr` instead.")
124
+ def _var_name_unwrapped(self) -> str:
125
+ """The name of the var without extra curly braces.
126
+
127
+ Returns:
128
+ The name of the var.
129
+ """
130
+ return self._js_expr
131
+
107
132
  @property
108
133
  def _var_is_string(self) -> bool:
109
134
  """Whether the var is a string literal.
@@ -116,11 +141,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
116
141
  def __post_init__(self):
117
142
  """Post-initialize the var."""
118
143
  # Decode any inline Var markup and apply it to the instance
119
- _var_data, _var_name = _decode_var_immutable(self._var_name)
144
+ _var_data, _js_expr = _decode_var_immutable(self._js_expr)
120
145
 
121
- if _var_data or _var_name != self._var_name:
146
+ if _var_data or _js_expr != self._js_expr:
122
147
  self.__init__(
123
- _var_name=_var_name,
148
+ _js_expr=_js_expr,
124
149
  _var_type=self._var_type,
125
150
  _var_data=VarData.merge(self._var_data, _var_data),
126
151
  )
@@ -131,7 +156,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
131
156
  Returns:
132
157
  The hash of the var.
133
158
  """
134
- return hash((self._var_name, self._var_type, self._var_data))
159
+ return hash((self._js_expr, self._var_type, self._var_data))
135
160
 
136
161
  def _get_all_var_data(self) -> VarData | None:
137
162
  """Get all VarData associated with the Var.
@@ -141,7 +166,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
141
166
  """
142
167
  return self._var_data
143
168
 
144
- def equals(self, other: ImmutableVar) -> bool:
169
+ def equals(self, other: Var) -> bool:
145
170
  """Check if two vars are equal.
146
171
 
147
172
  Args:
@@ -151,7 +176,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
151
176
  Whether the vars are equal.
152
177
  """
153
178
  return (
154
- self._var_name == other._var_name
179
+ self._js_expr == other._js_expr
155
180
  and self._var_type == other._var_type
156
181
  and self._get_all_var_data() == other._get_all_var_data()
157
182
  )
@@ -164,24 +189,20 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
164
189
  **kwargs: Var fields to update.
165
190
 
166
191
  Returns:
167
- A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
192
+ A new Var with the updated fields overwriting the corresponding fields in this Var.
168
193
 
169
194
  Raises:
170
195
  TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
171
196
  """
172
197
  if kwargs.get("_var_is_local", False) is not False:
173
- raise TypeError(
174
- "The _var_is_local argument is not supported for ImmutableVar."
175
- )
198
+ raise TypeError("The _var_is_local argument is not supported for Var.")
176
199
 
177
200
  if kwargs.get("_var_is_string", False) is not False:
178
- raise TypeError(
179
- "The _var_is_string argument is not supported for ImmutableVar."
180
- )
201
+ raise TypeError("The _var_is_string argument is not supported for Var.")
181
202
 
182
203
  if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
183
204
  raise TypeError(
184
- "The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
205
+ "The _var_full_name_needs_state_prefix argument is not supported for Var."
185
206
  )
186
207
 
187
208
  return dataclasses.replace(
@@ -199,7 +220,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
199
220
  _var_is_local: bool | None = None,
200
221
  _var_is_string: bool | None = None,
201
222
  _var_data: VarData | None = None,
202
- ) -> ImmutableVar | None:
223
+ ) -> Var:
203
224
  """Create a var from a value.
204
225
 
205
226
  Args:
@@ -210,80 +231,57 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
210
231
 
211
232
  Returns:
212
233
  The var.
213
-
214
- Raises:
215
- VarTypeError: If the value is JSON-unserializable.
216
- TypeError: If _var_is_local or _var_is_string is not None.
217
234
  """
218
235
  if _var_is_local is not None:
219
- raise TypeError(
220
- "The _var_is_local argument is not supported for ImmutableVar."
236
+ console.deprecate(
237
+ feature_name="_var_is_local",
238
+ reason="The _var_is_local argument is not supported for Var."
239
+ "If you want to create a Var from a raw Javascript expression, use the constructor directly",
240
+ deprecation_version="0.6.0",
241
+ removal_version="0.7.0",
221
242
  )
222
-
223
243
  if _var_is_string is not None:
224
- raise TypeError(
225
- "The _var_is_string argument is not supported for ImmutableVar."
244
+ console.deprecate(
245
+ feature_name="_var_is_string",
246
+ reason="The _var_is_string argument is not supported for Var."
247
+ "If you want to create a Var from a raw Javascript expression, use the constructor directly",
248
+ deprecation_version="0.6.0",
249
+ removal_version="0.7.0",
226
250
  )
227
251
 
228
- from reflex.utils import format
229
-
230
- # Check for none values.
231
- if value is None:
232
- return None
233
-
234
252
  # If the value is already a var, do nothing.
235
- if isinstance(value, ImmutableVar):
253
+ if isinstance(value, Var):
236
254
  return value
237
255
 
238
256
  # Try to pull the imports and hooks from contained values.
239
257
  if not isinstance(value, str):
240
- _var_data = VarData.merge(*_extract_var_data(value), _var_data)
258
+ return LiteralVar.create(value)
241
259
 
242
- # Try to serialize the value.
243
- type_ = type(value)
244
- if type_ in types.JSONType:
245
- name = value
246
- else:
247
- name, _serialized_type = serializers.serialize(value, get_type=True)
248
- if name is None:
249
- raise VarTypeError(
250
- f"No JSON serializer found for var {value} of type {type_}."
260
+ if _var_is_string is False or _var_is_local is True:
261
+ return cls(
262
+ _js_expr=value,
263
+ _var_data=_var_data,
251
264
  )
252
- name = name if isinstance(name, str) else format.json_dumps(name)
253
265
 
254
- return cls(
255
- _var_name=name,
256
- _var_type=type_,
257
- _var_data=_var_data,
258
- )
266
+ return LiteralVar.create(value, _var_data=_var_data)
259
267
 
260
268
  @classmethod
269
+ @deprecated("Use `.create()` instead.")
261
270
  def create_safe(
262
271
  cls,
263
- value: Any,
264
- _var_is_local: bool | None = None,
265
- _var_is_string: bool | None = None,
266
- _var_data: VarData | None = None,
267
- ) -> ImmutableVar:
268
- """Create a var from a value, asserting that it is not None.
272
+ *args: Any,
273
+ **kwargs: Any,
274
+ ) -> Var:
275
+ """Create a var from a value.
269
276
 
270
277
  Args:
271
- value: The value to create the var from.
272
- _var_is_local: Whether the var is local. Deprecated.
273
- _var_is_string: Whether the var is a string literal. Deprecated.
274
- _var_data: Additional hooks and imports associated with the Var.
278
+ *args: The arguments to create the var from.
279
+ **kwargs: The keyword arguments to create the var from.
275
280
 
276
281
  Returns:
277
282
  The var.
278
283
  """
279
- var = cls.create(
280
- value,
281
- _var_is_local=_var_is_local,
282
- _var_is_string=_var_is_string,
283
- _var_data=_var_data,
284
- )
285
- assert var is not None
286
- return var
284
+ return cls.create(*args, **kwargs)
287
285
 
288
286
  def __format__(self, format_spec: str) -> str:
289
287
  """Format the var into a Javascript equivalent to an f-string.
@@ -299,7 +297,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
299
297
  _global_vars[hashed_var] = self
300
298
 
301
299
  # Encode the _var_data into the formatted output for tracking purposes.
302
- return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._var_name}"
300
+ return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}"
303
301
 
304
302
  @overload
305
303
  def to(self, output: Type[StringVar]) -> ToStringOperation: ...
@@ -343,7 +341,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
343
341
  self,
344
342
  output: Type[OUTPUT] | types.GenericType,
345
343
  var_type: types.GenericType | None = None,
346
- ) -> ImmutableVar:
344
+ ) -> Var:
347
345
  """Convert the var to a different type.
348
346
 
349
347
  Args:
@@ -387,6 +385,12 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
387
385
  return self.to(BooleanVar, output)
388
386
  if fixed_output_type is None:
389
387
  return ToNoneOperation.create(self)
388
+ if issubclass(fixed_output_type, Base):
389
+ return self.to(ObjectVar, output)
390
+ if dataclasses.is_dataclass(fixed_output_type) and not issubclass(
391
+ fixed_output_type, Var
392
+ ):
393
+ return self.to(ObjectVar, output)
390
394
 
391
395
  if issubclass(output, BooleanVar):
392
396
  return ToBooleanVarOperation.create(self)
@@ -421,6 +425,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
421
425
  if issubclass(output, (ObjectVar, Base)):
422
426
  return ToObjectOperation.create(self, var_type or dict)
423
427
 
428
+ if dataclasses.is_dataclass(output):
429
+ return ToObjectOperation.create(self, var_type or dict)
430
+
424
431
  if issubclass(output, FunctionVar):
425
432
  # if fixed_type is not None and not issubclass(fixed_type, Callable):
426
433
  # raise TypeError(
@@ -447,11 +454,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
447
454
 
448
455
  return self
449
456
 
450
- def guess_type(self) -> ImmutableVar:
457
+ def guess_type(self) -> Var:
451
458
  """Guesses the type of the variable based on its `_var_type` attribute.
452
459
 
453
460
  Returns:
454
- ImmutableVar: The guessed type of the variable.
461
+ Var: The guessed type of the variable.
455
462
 
456
463
  Raises:
457
464
  TypeError: If the type is not supported for guessing.
@@ -479,7 +486,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
479
486
  ):
480
487
  return self.to(NumberVar, self._var_type)
481
488
 
482
- if all(inspect.isclass(t) and issubclass(t, Base) for t in inner_types):
489
+ if all(
490
+ inspect.isclass(t)
491
+ and (issubclass(t, Base) or dataclasses.is_dataclass(t))
492
+ for t in inner_types
493
+ ):
483
494
  return self.to(ObjectVar, self._var_type)
484
495
 
485
496
  return self
@@ -499,6 +510,8 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
499
510
  return self.to(StringVar, self._var_type)
500
511
  if issubclass(fixed_type, Base):
501
512
  return self.to(ObjectVar, self._var_type)
513
+ if dataclasses.is_dataclass(fixed_type):
514
+ return self.to(ObjectVar, self._var_type)
502
515
  return self
503
516
 
504
517
  def get_default_value(self) -> Any:
@@ -553,7 +566,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
553
566
  Returns:
554
567
  The name of the setter function.
555
568
  """
556
- var_name_parts = self._var_name.split(".")
569
+ var_name_parts = self._js_expr.split(".")
557
570
  setter = constants.SETTER_PREFIX + var_name_parts[-1]
558
571
  var_data = self._get_all_var_data()
559
572
  if var_data is None:
@@ -568,7 +581,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
568
581
  Returns:
569
582
  A function that that creates a setter for the var.
570
583
  """
571
- actual_name = self._var_name.split(".")[-1]
584
+ actual_name = self._js_expr.split(".")[-1]
572
585
 
573
586
  def setter(state: BaseState, value: Any):
574
587
  """Get the setter for the var.
@@ -583,7 +596,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
583
596
  setattr(state, actual_name, value)
584
597
  except ValueError:
585
598
  console.debug(
586
- f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
599
+ f"{type(state).__name__}.{self._js_expr}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
587
600
  )
588
601
  else:
589
602
  setattr(state, actual_name, value)
@@ -613,11 +626,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
613
626
  _var_data=VarData.merge(VarData.from_state(state), self._var_data),
614
627
  ).guess_type()
615
628
 
616
- def __eq__(self, other: ImmutableVar | Any) -> BooleanVar:
629
+ def __eq__(self, other: Var | Any) -> BooleanVar:
617
630
  """Check if the current variable is equal to the given variable.
618
631
 
619
632
  Args:
620
- other (ImmutableVar | Any): The variable to compare with.
633
+ other (Var | Any): The variable to compare with.
621
634
 
622
635
  Returns:
623
636
  BooleanVar: A BooleanVar object representing the result of the equality check.
@@ -626,11 +639,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
626
639
 
627
640
  return equal_operation(self, other)
628
641
 
629
- def __ne__(self, other: ImmutableVar | Any) -> BooleanVar:
642
+ def __ne__(self, other: Var | Any) -> BooleanVar:
630
643
  """Check if the current object is not equal to the given object.
631
644
 
632
645
  Parameters:
633
- other (ImmutableVar | Any): The object to compare with.
646
+ other (Var | Any): The object to compare with.
634
647
 
635
648
  Returns:
636
649
  BooleanVar: A BooleanVar object representing the result of the comparison.
@@ -649,7 +662,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
649
662
 
650
663
  return boolify(self)
651
664
 
652
- def __and__(self, other: ImmutableVar | Any) -> ImmutableVar:
665
+ def __and__(self, other: Var | Any) -> Var:
653
666
  """Perform a logical AND operation on the current instance and another variable.
654
667
 
655
668
  Args:
@@ -660,7 +673,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
660
673
  """
661
674
  return and_operation(self, other)
662
675
 
663
- def __rand__(self, other: ImmutableVar | Any) -> ImmutableVar:
676
+ def __rand__(self, other: Var | Any) -> Var:
664
677
  """Perform a logical AND operation on the current instance and another variable.
665
678
 
666
679
  Args:
@@ -671,7 +684,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
671
684
  """
672
685
  return and_operation(other, self)
673
686
 
674
- def __or__(self, other: ImmutableVar | Any) -> ImmutableVar:
687
+ def __or__(self, other: Var | Any) -> Var:
675
688
  """Perform a logical OR operation on the current instance and another variable.
676
689
 
677
690
  Args:
@@ -682,7 +695,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
682
695
  """
683
696
  return or_operation(self, other)
684
697
 
685
- def __ror__(self, other: ImmutableVar | Any) -> ImmutableVar:
698
+ def __ror__(self, other: Var | Any) -> Var:
686
699
  """Perform a logical OR operation on the current instance and another variable.
687
700
 
688
701
  Args:
@@ -712,7 +725,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
712
725
 
713
726
  return JSON_STRINGIFY.call(self).to(StringVar)
714
727
 
715
- def as_ref(self) -> ImmutableVar:
728
+ def as_ref(self) -> Var:
716
729
  """Get a reference to the var.
717
730
 
718
731
  Returns:
@@ -720,14 +733,14 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
720
733
  """
721
734
  from .object import ObjectVar
722
735
 
723
- refs = ImmutableVar(
724
- _var_name="refs",
736
+ refs = Var(
737
+ _js_expr="refs",
725
738
  _var_data=VarData(
726
739
  imports={
727
740
  f"/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
728
741
  }
729
742
  ),
730
- ).to(ObjectVar)
743
+ ).to(ObjectVar, Dict[str, str])
731
744
  return refs[LiteralVar.create(str(self))]
732
745
 
733
746
  @deprecated("Use `.js_type()` instead.")
@@ -813,7 +826,8 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
813
826
  TypeError: If the var type is Any.
814
827
  """
815
828
  if name.startswith("_"):
816
- return super(ImmutableVar, self).__getattribute__(name)
829
+ return self.__getattribute__(name)
830
+
817
831
  if self._var_type is Any:
818
832
  raise TypeError(
819
833
  f"You must provide an annotation for the state var `{str(self)}`. Annotation cannot be `{self._var_type}`."
@@ -857,26 +871,85 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
857
871
  var_data = self._get_all_var_data()
858
872
  return var_data.state if var_data else ""
859
873
 
874
+ @overload
875
+ @classmethod
876
+ def range(cls, stop: int | NumberVar, /) -> ArrayVar[List[int]]: ...
877
+
878
+ @overload
879
+ @classmethod
880
+ def range(
881
+ cls,
882
+ start: int | NumberVar,
883
+ end: int | NumberVar,
884
+ step: int | NumberVar = 1,
885
+ /,
886
+ ) -> ArrayVar[List[int]]: ...
887
+
888
+ @classmethod
889
+ def range(
890
+ cls,
891
+ first_endpoint: int | NumberVar,
892
+ second_endpoint: int | NumberVar | None = None,
893
+ step: int | NumberVar | None = None,
894
+ ) -> ArrayVar[List[int]]:
895
+ """Create a range of numbers.
860
896
 
861
- OUTPUT = TypeVar("OUTPUT", bound=ImmutableVar)
897
+ Args:
898
+ first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
899
+ second_endpoint: The end of the range.
900
+ step: The step of the range.
862
901
 
902
+ Returns:
903
+ The range of numbers.
904
+ """
905
+ from .sequence import ArrayVar
863
906
 
864
- def _encode_var(value: ImmutableVar) -> str:
865
- """Encode the state name into a formatted var.
907
+ return ArrayVar.range(first_endpoint, second_endpoint, step)
866
908
 
867
- Args:
868
- value: The value to encode the state name into.
909
+ def __bool__(self) -> bool:
910
+ """Raise exception if using Var in a boolean context.
869
911
 
870
- Returns:
871
- The encoded var.
872
- """
873
- return f"{value}"
912
+ Raises:
913
+ VarTypeError: when attempting to bool-ify the Var.
914
+ """
915
+ raise VarTypeError(
916
+ f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
917
+ "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
918
+ )
919
+
920
+ def __iter__(self) -> Any:
921
+ """Raise exception if using Var in an iterable context.
922
+
923
+ Raises:
924
+ VarTypeError: when attempting to iterate over the Var.
925
+ """
926
+ raise VarTypeError(
927
+ f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
928
+ )
929
+
930
+ def __contains__(self, _: Any) -> Var:
931
+ """Override the 'in' operator to alert the user that it is not supported.
932
+
933
+ Raises:
934
+ VarTypeError: the operation is not supported
935
+ """
936
+ raise VarTypeError(
937
+ "'in' operator not supported for Var types, use Var.contains() instead."
938
+ )
939
+
940
+ def json(self) -> str:
941
+ """Serialize the var to a JSON string.
942
+
943
+ Raises:
944
+ NotImplementedError: If the method is not implemented.
945
+ """
946
+ raise NotImplementedError("Var subclasses must implement the json method.")
874
947
 
875
948
 
876
- serializers.serializer(_encode_var)
949
+ OUTPUT = TypeVar("OUTPUT", bound=Var)
877
950
 
878
951
 
879
- class LiteralVar(ImmutableVar):
952
+ class LiteralVar(Var):
880
953
  """Base class for immutable literal vars."""
881
954
 
882
955
  @classmethod
@@ -884,7 +957,7 @@ class LiteralVar(ImmutableVar):
884
957
  cls,
885
958
  value: Any,
886
959
  _var_data: VarData | None = None,
887
- ) -> ImmutableVar:
960
+ ) -> Var:
888
961
  """Create a var from a value.
889
962
 
890
963
  Args:
@@ -901,7 +974,7 @@ class LiteralVar(ImmutableVar):
901
974
  from .object import LiteralObjectVar
902
975
  from .sequence import LiteralArrayVar, LiteralStringVar
903
976
 
904
- if isinstance(value, ImmutableVar):
977
+ if isinstance(value, Var):
905
978
  if _var_data is None:
906
979
  return value
907
980
  return value._replace(merge_var_data=_var_data)
@@ -924,7 +997,7 @@ class LiteralVar(ImmutableVar):
924
997
  if value is None:
925
998
  return LiteralNoneVar.create(_var_data=_var_data)
926
999
 
927
- from reflex.event import EventChain, EventSpec
1000
+ from reflex.event import EventChain, EventHandler, EventSpec
928
1001
  from reflex.utils.format import get_event_handler_parts
929
1002
 
930
1003
  from .function import ArgsFunctionOperation, FunctionStringVar
@@ -948,14 +1021,12 @@ class LiteralVar(ImmutableVar):
948
1021
  sig = inspect.signature(value.args_spec) # type: ignore
949
1022
  if sig.parameters:
950
1023
  arg_def = tuple((f"_{p}" for p in sig.parameters))
951
- arg_def_expr = LiteralVar.create(
952
- [ImmutableVar.create_safe(arg) for arg in arg_def]
953
- )
1024
+ arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
954
1025
  else:
955
1026
  # add a default argument for addEvents if none were specified in value.args_spec
956
1027
  # used to trigger the preventDefault() on the event.
957
1028
  arg_def = ("...args",)
958
- arg_def_expr = ImmutableVar.create_safe("args")
1029
+ arg_def_expr = Var(_js_expr="args")
959
1030
 
960
1031
  return ArgsFunctionOperation.create(
961
1032
  arg_def,
@@ -968,12 +1039,8 @@ class LiteralVar(ImmutableVar):
968
1039
  ),
969
1040
  )
970
1041
 
971
- if isinstance(value, Base):
972
- return LiteralObjectVar.create(
973
- {k: (None if callable(v) else v) for k, v in value.dict().items()},
974
- _var_type=type(value),
975
- _var_data=_var_data,
976
- )
1042
+ if isinstance(value, EventHandler):
1043
+ return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value))))
977
1044
 
978
1045
  serialized_value = serializers.serialize(value)
979
1046
  if serialized_value is not None:
@@ -983,8 +1050,37 @@ class LiteralVar(ImmutableVar):
983
1050
  _var_type=type(value),
984
1051
  _var_data=_var_data,
985
1052
  )
1053
+ if isinstance(serialized_value, str):
1054
+ return LiteralStringVar.create(
1055
+ serialized_value, _var_type=type(value), _var_data=_var_data
1056
+ )
986
1057
  return LiteralVar.create(serialized_value, _var_data=_var_data)
987
1058
 
1059
+ if isinstance(value, Base):
1060
+ # get the fields of the pydantic class
1061
+ fields = value.__fields__.keys()
1062
+ one_level_dict = {field: getattr(value, field) for field in fields}
1063
+
1064
+ return LiteralObjectVar.create(
1065
+ {
1066
+ field: value
1067
+ for field, value in one_level_dict.items()
1068
+ if not callable(value)
1069
+ },
1070
+ _var_type=type(value),
1071
+ _var_data=_var_data,
1072
+ )
1073
+
1074
+ if dataclasses.is_dataclass(value) and not isinstance(value, type):
1075
+ return LiteralObjectVar.create(
1076
+ {
1077
+ k: (None if callable(v) else v)
1078
+ for k, v in dataclasses.asdict(value).items()
1079
+ },
1080
+ _var_type=type(value),
1081
+ _var_data=_var_data,
1082
+ )
1083
+
988
1084
  raise TypeError(
989
1085
  f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
990
1086
  )
@@ -1003,6 +1099,19 @@ class LiteralVar(ImmutableVar):
1003
1099
  )
1004
1100
 
1005
1101
 
1102
+ @serializers.serializer
1103
+ def serialize_literal(value: LiteralVar):
1104
+ """Serialize a Literal type.
1105
+
1106
+ Args:
1107
+ value: The Literal to serialize.
1108
+
1109
+ Returns:
1110
+ The serialized Literal.
1111
+ """
1112
+ return serializers.serialize(value._var_value)
1113
+
1114
+
1006
1115
  P = ParamSpec("P")
1007
1116
  T = TypeVar("T")
1008
1117
 
@@ -1011,7 +1120,7 @@ T = TypeVar("T")
1011
1120
  @overload
1012
1121
  def var_operation(
1013
1122
  func: Callable[P, CustomVarOperationReturn[NoReturn]],
1014
- ) -> Callable[P, ImmutableVar]: ...
1123
+ ) -> Callable[P, Var]: ...
1015
1124
 
1016
1125
 
1017
1126
  @overload
@@ -1055,7 +1164,7 @@ def var_operation(
1055
1164
 
1056
1165
  def var_operation(
1057
1166
  func: Callable[P, CustomVarOperationReturn[T]],
1058
- ) -> Callable[P, ImmutableVar[T]]:
1167
+ ) -> Callable[P, Var[T]]:
1059
1168
  """Decorator for creating a var operation.
1060
1169
 
1061
1170
  Example:
@@ -1073,18 +1182,14 @@ def var_operation(
1073
1182
  """
1074
1183
 
1075
1184
  @functools.wraps(func)
1076
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
1185
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Var[T]:
1077
1186
  func_args = list(inspect.signature(func).parameters)
1078
1187
  args_vars = {
1079
- func_args[i]: (
1080
- LiteralVar.create(arg) if not isinstance(arg, ImmutableVar) else arg
1081
- )
1188
+ func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg)
1082
1189
  for i, arg in enumerate(args)
1083
1190
  }
1084
1191
  kwargs_vars = {
1085
- key: LiteralVar.create(value)
1086
- if not isinstance(value, ImmutableVar)
1087
- else value
1192
+ key: LiteralVar.create(value) if not isinstance(value, Var) else value
1088
1193
  for key, value in kwargs.items()
1089
1194
  }
1090
1195
 
@@ -1107,10 +1212,13 @@ def unionize(*args: Type) -> Type:
1107
1212
  """
1108
1213
  if not args:
1109
1214
  return Any
1110
- first, *rest = args
1111
- if not rest:
1112
- return first
1113
- return Union[first, unionize(*rest)]
1215
+ if len(args) == 1:
1216
+ return args[0]
1217
+ # We are bisecting the args list here to avoid hitting the recursion limit
1218
+ # In Python versions >= 3.11, we can simply do `return Union[*args]`
1219
+ midpoint = len(args) // 2
1220
+ first_half, second_half = args[:midpoint], args[midpoint:]
1221
+ return Union[unionize(*first_half), unionize(*second_half)]
1114
1222
 
1115
1223
 
1116
1224
  def figure_out_type(value: Any) -> types.GenericType:
@@ -1133,7 +1241,7 @@ def figure_out_type(value: Any) -> types.GenericType:
1133
1241
  unionize(*(figure_out_type(k) for k in value)),
1134
1242
  unionize(*(figure_out_type(v) for v in value.values())),
1135
1243
  ]
1136
- if isinstance(value, ImmutableVar):
1244
+ if isinstance(value, Var):
1137
1245
  return value._var_type
1138
1246
  return type(value)
1139
1247
 
@@ -1156,7 +1264,7 @@ class CachedVarOperation:
1156
1264
 
1157
1265
  def __post_init__(self):
1158
1266
  """Post-initialize the CachedVarOperation."""
1159
- object.__delattr__(self, "_var_name")
1267
+ object.__delattr__(self, "_js_expr")
1160
1268
 
1161
1269
  def __getattr__(self, name: str) -> Any:
1162
1270
  """Get an attribute of the var.
@@ -1167,7 +1275,7 @@ class CachedVarOperation:
1167
1275
  Returns:
1168
1276
  The attribute.
1169
1277
  """
1170
- if name == "_var_name":
1278
+ if name == "_js_expr":
1171
1279
  return self._cached_var_name
1172
1280
 
1173
1281
  parent_classes = inspect.getmro(self.__class__)
@@ -1194,9 +1302,7 @@ class CachedVarOperation:
1194
1302
  return VarData.merge(
1195
1303
  *map(
1196
1304
  lambda value: (
1197
- value._get_all_var_data()
1198
- if isinstance(value, ImmutableVar)
1199
- else None
1305
+ value._get_all_var_data() if isinstance(value, Var) else None
1200
1306
  ),
1201
1307
  map(
1202
1308
  lambda field: getattr(self, field.name),
@@ -1218,13 +1324,13 @@ class CachedVarOperation:
1218
1324
  *[
1219
1325
  getattr(self, field.name)
1220
1326
  for field in dataclasses.fields(self) # type: ignore
1221
- if field.name not in ["_var_name", "_var_data", "_var_type"]
1327
+ if field.name not in ["_js_expr", "_var_data", "_var_type"]
1222
1328
  ],
1223
1329
  )
1224
1330
  )
1225
1331
 
1226
1332
 
1227
- def and_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
1333
+ def and_operation(a: Var | Any, b: Var | Any) -> Var:
1228
1334
  """Perform a logical AND operation on two variables.
1229
1335
 
1230
1336
  Args:
@@ -1238,7 +1344,7 @@ def and_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
1238
1344
 
1239
1345
 
1240
1346
  @var_operation
1241
- def _and_operation(a: ImmutableVar, b: ImmutableVar):
1347
+ def _and_operation(a: Var, b: Var):
1242
1348
  """Perform a logical AND operation on two variables.
1243
1349
 
1244
1350
  Args:
@@ -1254,7 +1360,7 @@ def _and_operation(a: ImmutableVar, b: ImmutableVar):
1254
1360
  )
1255
1361
 
1256
1362
 
1257
- def or_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
1363
+ def or_operation(a: Var | Any, b: Var | Any) -> Var:
1258
1364
  """Perform a logical OR operation on two variables.
1259
1365
 
1260
1366
  Args:
@@ -1268,7 +1374,7 @@ def or_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
1268
1374
 
1269
1375
 
1270
1376
  @var_operation
1271
- def _or_operation(a: ImmutableVar, b: ImmutableVar):
1377
+ def _or_operation(a: Var, b: Var):
1272
1378
  """Perform a logical OR operation on two variables.
1273
1379
 
1274
1380
  Args:
@@ -1289,36 +1395,36 @@ def _or_operation(a: ImmutableVar, b: ImmutableVar):
1289
1395
  frozen=True,
1290
1396
  **{"slots": True} if sys.version_info >= (3, 10) else {},
1291
1397
  )
1292
- class ImmutableCallableVar(ImmutableVar):
1398
+ class CallableVar(Var):
1293
1399
  """Decorate a Var-returning function to act as both a Var and a function.
1294
1400
 
1295
1401
  This is used as a compatibility shim for replacing Var objects in the
1296
1402
  API with functions that return a family of Var.
1297
1403
  """
1298
1404
 
1299
- fn: Callable[..., ImmutableVar] = dataclasses.field(
1300
- default_factory=lambda: lambda: ImmutableVar(_var_name="undefined")
1405
+ fn: Callable[..., Var] = dataclasses.field(
1406
+ default_factory=lambda: lambda: Var(_js_expr="undefined")
1301
1407
  )
1302
- original_var: ImmutableVar = dataclasses.field(
1303
- default_factory=lambda: ImmutableVar(_var_name="undefined")
1408
+ original_var: Var = dataclasses.field(
1409
+ default_factory=lambda: Var(_js_expr="undefined")
1304
1410
  )
1305
1411
 
1306
- def __init__(self, fn: Callable[..., ImmutableVar]):
1412
+ def __init__(self, fn: Callable[..., Var]):
1307
1413
  """Initialize a CallableVar.
1308
1414
 
1309
1415
  Args:
1310
1416
  fn: The function to decorate (must return Var)
1311
1417
  """
1312
1418
  original_var = fn()
1313
- super(ImmutableCallableVar, self).__init__(
1314
- _var_name=original_var._var_name,
1419
+ super(CallableVar, self).__init__(
1420
+ _js_expr=original_var._js_expr,
1315
1421
  _var_type=original_var._var_type,
1316
1422
  _var_data=VarData.merge(original_var._get_all_var_data()),
1317
1423
  )
1318
1424
  object.__setattr__(self, "fn", fn)
1319
1425
  object.__setattr__(self, "original_var", original_var)
1320
1426
 
1321
- def __call__(self, *args, **kwargs) -> ImmutableVar:
1427
+ def __call__(self, *args, **kwargs) -> Var:
1322
1428
  """Call the decorated function.
1323
1429
 
1324
1430
  Args:
@@ -1347,13 +1453,13 @@ DICT_VAL = TypeVar("DICT_VAL")
1347
1453
  LIST_INSIDE = TypeVar("LIST_INSIDE")
1348
1454
 
1349
1455
 
1350
- class FakeComputedVarBaseClass(Var, property):
1456
+ class FakeComputedVarBaseClass(property):
1351
1457
  """A fake base class for ComputedVar to avoid inheriting from property."""
1352
1458
 
1353
1459
  __pydantic_run_validation__ = False
1354
1460
 
1355
1461
 
1356
- def is_computed_var(obj: Any) -> TypeGuard[ImmutableComputedVar]:
1462
+ def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]:
1357
1463
  """Check if the object is a ComputedVar.
1358
1464
 
1359
1465
  Args:
@@ -1370,7 +1476,7 @@ def is_computed_var(obj: Any) -> TypeGuard[ImmutableComputedVar]:
1370
1476
  frozen=True,
1371
1477
  **{"slots": True} if sys.version_info >= (3, 10) else {},
1372
1478
  )
1373
- class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1479
+ class ComputedVar(Var[RETURN_TYPE]):
1374
1480
  """A field with computed getters."""
1375
1481
 
1376
1482
  # Whether to track dependencies and cache computed values
@@ -1400,7 +1506,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1400
1506
  fget: Callable[[BASE_STATE], RETURN_TYPE],
1401
1507
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
1402
1508
  cache: bool = False,
1403
- deps: Optional[List[Union[str, ImmutableVar]]] = None,
1509
+ deps: Optional[List[Union[str, Var]]] = None,
1404
1510
  auto_deps: bool = True,
1405
1511
  interval: Optional[Union[int, datetime.timedelta]] = None,
1406
1512
  backend: bool | None = None,
@@ -1424,12 +1530,12 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1424
1530
  hints = get_type_hints(fget)
1425
1531
  hint = hints.get("return", Any)
1426
1532
 
1427
- kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
1533
+ kwargs["_js_expr"] = kwargs.pop("_js_expr", fget.__name__)
1428
1534
  kwargs["_var_type"] = kwargs.pop("_var_type", hint)
1429
1535
 
1430
- ImmutableVar.__init__(
1536
+ Var.__init__(
1431
1537
  self,
1432
- _var_name=kwargs.pop("_var_name"),
1538
+ _js_expr=kwargs.pop("_js_expr"),
1433
1539
  _var_type=kwargs.pop("_var_type"),
1434
1540
  _var_data=kwargs.pop("_var_data", None),
1435
1541
  )
@@ -1450,7 +1556,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1450
1556
  deps = []
1451
1557
  else:
1452
1558
  for dep in deps:
1453
- if isinstance(dep, ImmutableVar):
1559
+ if isinstance(dep, Var):
1454
1560
  continue
1455
1561
  if isinstance(dep, str) and dep != "":
1456
1562
  continue
@@ -1460,7 +1566,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1460
1566
  object.__setattr__(
1461
1567
  self,
1462
1568
  "_static_deps",
1463
- {dep._var_name if isinstance(dep, ImmutableVar) else dep for dep in deps},
1569
+ {dep._js_expr if isinstance(dep, Var) else dep for dep in deps},
1464
1570
  )
1465
1571
  object.__setattr__(self, "_auto_deps", auto_deps)
1466
1572
 
@@ -1488,7 +1594,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1488
1594
  auto_deps=kwargs.pop("auto_deps", self._auto_deps),
1489
1595
  interval=kwargs.pop("interval", self._update_interval),
1490
1596
  backend=kwargs.pop("backend", self._backend),
1491
- _var_name=kwargs.pop("_var_name", self._var_name),
1597
+ _js_expr=kwargs.pop("_js_expr", self._js_expr),
1492
1598
  _var_type=kwargs.pop("_var_type", self._var_type),
1493
1599
  _var_data=kwargs.pop(
1494
1600
  "_var_data", VarData.merge(self._var_data, merge_var_data)
@@ -1508,7 +1614,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1508
1614
  Returns:
1509
1615
  An attribute name.
1510
1616
  """
1511
- return f"__cached_{self._var_name}"
1617
+ return f"__cached_{self._js_expr}"
1512
1618
 
1513
1619
  @property
1514
1620
  def _last_updated_attr(self) -> str:
@@ -1517,7 +1623,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1517
1623
  Returns:
1518
1624
  An attribute name.
1519
1625
  """
1520
- return f"__last_updated_{self._var_name}"
1626
+ return f"__last_updated_{self._js_expr}"
1521
1627
 
1522
1628
  def needs_update(self, instance: BaseState) -> bool:
1523
1629
  """Check if the computed var needs to be updated.
@@ -1537,50 +1643,48 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1537
1643
 
1538
1644
  @overload
1539
1645
  def __get__(
1540
- self: ImmutableComputedVar[int] | ImmutableComputedVar[float],
1646
+ self: ComputedVar[int] | ComputedVar[float],
1541
1647
  instance: None,
1542
1648
  owner: Type,
1543
1649
  ) -> NumberVar: ...
1544
1650
 
1545
1651
  @overload
1546
1652
  def __get__(
1547
- self: ImmutableComputedVar[str],
1653
+ self: ComputedVar[str],
1548
1654
  instance: None,
1549
1655
  owner: Type,
1550
1656
  ) -> StringVar: ...
1551
1657
 
1552
1658
  @overload
1553
1659
  def __get__(
1554
- self: ImmutableComputedVar[dict[DICT_KEY, DICT_VAL]],
1660
+ self: ComputedVar[dict[DICT_KEY, DICT_VAL]],
1555
1661
  instance: None,
1556
1662
  owner: Type,
1557
1663
  ) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
1558
1664
 
1559
1665
  @overload
1560
1666
  def __get__(
1561
- self: ImmutableComputedVar[list[LIST_INSIDE]],
1667
+ self: ComputedVar[list[LIST_INSIDE]],
1562
1668
  instance: None,
1563
1669
  owner: Type,
1564
1670
  ) -> ArrayVar[list[LIST_INSIDE]]: ...
1565
1671
 
1566
1672
  @overload
1567
1673
  def __get__(
1568
- self: ImmutableComputedVar[set[LIST_INSIDE]],
1674
+ self: ComputedVar[set[LIST_INSIDE]],
1569
1675
  instance: None,
1570
1676
  owner: Type,
1571
1677
  ) -> ArrayVar[set[LIST_INSIDE]]: ...
1572
1678
 
1573
1679
  @overload
1574
1680
  def __get__(
1575
- self: ImmutableComputedVar[tuple[LIST_INSIDE, ...]],
1681
+ self: ComputedVar[tuple[LIST_INSIDE, ...]],
1576
1682
  instance: None,
1577
1683
  owner: Type,
1578
1684
  ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
1579
1685
 
1580
1686
  @overload
1581
- def __get__(
1582
- self, instance: None, owner: Type
1583
- ) -> ImmutableComputedVar[RETURN_TYPE]: ...
1687
+ def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ...
1584
1688
 
1585
1689
  @overload
1586
1690
  def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
@@ -1599,13 +1703,13 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1599
1703
  """
1600
1704
  if instance is None:
1601
1705
  state_where_defined = owner
1602
- while self.fget.__name__ in state_where_defined.inherited_vars:
1706
+ while self._js_expr in state_where_defined.inherited_vars:
1603
1707
  state_where_defined = state_where_defined.get_parent_state()
1604
1708
 
1605
1709
  return self._replace(
1606
- _var_name=format_state_name(state_where_defined.get_full_name())
1710
+ _js_expr=format_state_name(state_where_defined.get_full_name())
1607
1711
  + "."
1608
- + self._var_name,
1712
+ + self._js_expr,
1609
1713
  merge_var_data=VarData.from_state(state_where_defined),
1610
1714
  ).guess_type()
1611
1715
 
@@ -1705,7 +1809,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1705
1809
  )
1706
1810
  # recurse into property fget functions
1707
1811
  elif isinstance(ref_obj, property) and not isinstance(
1708
- ref_obj, ImmutableComputedVar
1812
+ ref_obj, ComputedVar
1709
1813
  ):
1710
1814
  d.update(
1711
1815
  self._deps(
@@ -1773,7 +1877,7 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
1773
1877
  return self._fget
1774
1878
 
1775
1879
 
1776
- class DynamicRouteVar(ImmutableComputedVar[Union[str, List[str]]]):
1880
+ class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
1777
1881
  """A ComputedVar that represents a dynamic route."""
1778
1882
 
1779
1883
  pass
@@ -1784,45 +1888,41 @@ if TYPE_CHECKING:
1784
1888
 
1785
1889
 
1786
1890
  @overload
1787
- def immutable_computed_var(
1891
+ def computed_var(
1788
1892
  fget: None = None,
1789
1893
  initial_value: Any | types.Unset = types.Unset(),
1790
1894
  cache: bool = False,
1791
- deps: Optional[List[Union[str, ImmutableVar]]] = None,
1895
+ deps: Optional[List[Union[str, Var]]] = None,
1792
1896
  auto_deps: bool = True,
1793
1897
  interval: Optional[Union[datetime.timedelta, int]] = None,
1794
1898
  backend: bool | None = None,
1795
1899
  **kwargs,
1796
- ) -> Callable[
1797
- [Callable[[BASE_STATE], RETURN_TYPE]], ImmutableComputedVar[RETURN_TYPE]
1798
- ]: ...
1900
+ ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ...
1799
1901
 
1800
1902
 
1801
1903
  @overload
1802
- def immutable_computed_var(
1904
+ def computed_var(
1803
1905
  fget: Callable[[BASE_STATE], RETURN_TYPE],
1804
1906
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
1805
1907
  cache: bool = False,
1806
- deps: Optional[List[Union[str, ImmutableVar]]] = None,
1908
+ deps: Optional[List[Union[str, Var]]] = None,
1807
1909
  auto_deps: bool = True,
1808
1910
  interval: Optional[Union[datetime.timedelta, int]] = None,
1809
1911
  backend: bool | None = None,
1810
1912
  **kwargs,
1811
- ) -> ImmutableComputedVar[RETURN_TYPE]: ...
1913
+ ) -> ComputedVar[RETURN_TYPE]: ...
1812
1914
 
1813
1915
 
1814
- def immutable_computed_var(
1916
+ def computed_var(
1815
1917
  fget: Callable[[BASE_STATE], Any] | None = None,
1816
1918
  initial_value: Any | types.Unset = types.Unset(),
1817
1919
  cache: bool = False,
1818
- deps: Optional[List[Union[str, ImmutableVar]]] = None,
1920
+ deps: Optional[List[Union[str, Var]]] = None,
1819
1921
  auto_deps: bool = True,
1820
1922
  interval: Optional[Union[datetime.timedelta, int]] = None,
1821
1923
  backend: bool | None = None,
1822
1924
  **kwargs,
1823
- ) -> (
1824
- ImmutableComputedVar | Callable[[Callable[[BASE_STATE], Any]], ImmutableComputedVar]
1825
- ):
1925
+ ) -> ComputedVar | Callable[[Callable[[BASE_STATE], Any]], ComputedVar]:
1826
1926
  """A ComputedVar decorator with or without kwargs.
1827
1927
 
1828
1928
  Args:
@@ -1849,10 +1949,10 @@ def immutable_computed_var(
1849
1949
  raise VarDependencyError("Cannot track dependencies without caching.")
1850
1950
 
1851
1951
  if fget is not None:
1852
- return ImmutableComputedVar(fget, cache=cache)
1952
+ return ComputedVar(fget, cache=cache)
1853
1953
 
1854
- def wrapper(fget: Callable[[BASE_STATE], Any]) -> ImmutableComputedVar:
1855
- return ImmutableComputedVar(
1954
+ def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
1955
+ return ComputedVar(
1856
1956
  fget,
1857
1957
  initial_value=initial_value,
1858
1958
  cache=cache,
@@ -1869,7 +1969,7 @@ def immutable_computed_var(
1869
1969
  RETURN = TypeVar("RETURN")
1870
1970
 
1871
1971
 
1872
- class CustomVarOperationReturn(ImmutableVar[RETURN]):
1972
+ class CustomVarOperationReturn(Var[RETURN]):
1873
1973
  """Base class for custom var operations."""
1874
1974
 
1875
1975
  @classmethod
@@ -1890,7 +1990,7 @@ class CustomVarOperationReturn(ImmutableVar[RETURN]):
1890
1990
  The CustomVarOperation.
1891
1991
  """
1892
1992
  return CustomVarOperationReturn(
1893
- _var_name=js_expression,
1993
+ _js_expr=js_expression,
1894
1994
  _var_type=_var_type or Any,
1895
1995
  _var_data=_var_data,
1896
1996
  )
@@ -1899,17 +1999,23 @@ class CustomVarOperationReturn(ImmutableVar[RETURN]):
1899
1999
  def var_operation_return(
1900
2000
  js_expression: str,
1901
2001
  var_type: Type[RETURN] | None = None,
2002
+ var_data: VarData | None = None,
1902
2003
  ) -> CustomVarOperationReturn[RETURN]:
1903
2004
  """Shortcut for creating a CustomVarOperationReturn.
1904
2005
 
1905
2006
  Args:
1906
2007
  js_expression: The JavaScript expression to evaluate.
1907
2008
  var_type: The type of the var.
2009
+ var_data: Additional hooks and imports associated with the Var.
1908
2010
 
1909
2011
  Returns:
1910
2012
  The CustomVarOperationReturn.
1911
2013
  """
1912
- return CustomVarOperationReturn.create(js_expression, var_type)
2014
+ return CustomVarOperationReturn.create(
2015
+ js_expression,
2016
+ var_type,
2017
+ var_data,
2018
+ )
1913
2019
 
1914
2020
 
1915
2021
  @dataclasses.dataclass(
@@ -1917,12 +2023,10 @@ def var_operation_return(
1917
2023
  frozen=True,
1918
2024
  **{"slots": True} if sys.version_info >= (3, 10) else {},
1919
2025
  )
1920
- class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
2026
+ class CustomVarOperation(CachedVarOperation, Var[T]):
1921
2027
  """Base class for custom var operations."""
1922
2028
 
1923
- _args: Tuple[Tuple[str, ImmutableVar], ...] = dataclasses.field(
1924
- default_factory=tuple
1925
- )
2029
+ _args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
1926
2030
 
1927
2031
  _return: CustomVarOperationReturn[T] = dataclasses.field(
1928
2032
  default_factory=lambda: CustomVarOperationReturn.create("")
@@ -1956,7 +2060,7 @@ class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
1956
2060
  @classmethod
1957
2061
  def create(
1958
2062
  cls,
1959
- args: Tuple[Tuple[str, ImmutableVar], ...],
2063
+ args: Tuple[Tuple[str, Var], ...],
1960
2064
  return_var: CustomVarOperationReturn[T],
1961
2065
  _var_data: VarData | None = None,
1962
2066
  ) -> CustomVarOperation[T]:
@@ -1971,7 +2075,7 @@ class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
1971
2075
  The CustomVarOperation.
1972
2076
  """
1973
2077
  return CustomVarOperation(
1974
- _var_name="",
2078
+ _js_expr="",
1975
2079
  _var_type=return_var._var_type,
1976
2080
  _var_data=_var_data,
1977
2081
  _args=args,
@@ -1979,7 +2083,7 @@ class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
1979
2083
  )
1980
2084
 
1981
2085
 
1982
- class NoneVar(ImmutableVar[None]):
2086
+ class NoneVar(Var[None]):
1983
2087
  """A var representing None."""
1984
2088
 
1985
2089
 
@@ -2008,7 +2112,7 @@ class LiteralNoneVar(LiteralVar, NoneVar):
2008
2112
  The var.
2009
2113
  """
2010
2114
  return LiteralNoneVar(
2011
- _var_name="null",
2115
+ _js_expr="null",
2012
2116
  _var_type=None,
2013
2117
  _var_data=_var_data,
2014
2118
  )
@@ -2051,7 +2155,7 @@ class ToNoneOperation(CachedVarOperation, NoneVar):
2051
2155
  The ToNoneOperation.
2052
2156
  """
2053
2157
  return ToNoneOperation(
2054
- _var_name="",
2158
+ _js_expr="",
2055
2159
  _var_type=None,
2056
2160
  _var_data=_var_data,
2057
2161
  _original_var=var,
@@ -2063,7 +2167,7 @@ class ToNoneOperation(CachedVarOperation, NoneVar):
2063
2167
  frozen=True,
2064
2168
  **{"slots": True} if sys.version_info >= (3, 10) else {},
2065
2169
  )
2066
- class StateOperation(CachedVarOperation, ImmutableVar):
2170
+ class StateOperation(CachedVarOperation, Var):
2067
2171
  """A var operation that accesses a field on an object."""
2068
2172
 
2069
2173
  _state_name: str = dataclasses.field(default="")
@@ -2087,7 +2191,7 @@ class StateOperation(CachedVarOperation, ImmutableVar):
2087
2191
  Returns:
2088
2192
  The attribute.
2089
2193
  """
2090
- if name == "_var_name":
2194
+ if name == "_js_expr":
2091
2195
  return self._cached_var_name
2092
2196
 
2093
2197
  return getattr(self._field, name)
@@ -2096,7 +2200,7 @@ class StateOperation(CachedVarOperation, ImmutableVar):
2096
2200
  def create(
2097
2201
  cls,
2098
2202
  state_name: str,
2099
- field: ImmutableVar,
2203
+ field: Var,
2100
2204
  _var_data: VarData | None = None,
2101
2205
  ) -> StateOperation:
2102
2206
  """Create a DotOperation.
@@ -2110,7 +2214,7 @@ class StateOperation(CachedVarOperation, ImmutableVar):
2110
2214
  The DotOperation.
2111
2215
  """
2112
2216
  return StateOperation(
2113
- _var_name="",
2217
+ _js_expr="",
2114
2218
  _var_type=field._var_type,
2115
2219
  _var_data=_var_data,
2116
2220
  _state_name=state_name,
@@ -2134,7 +2238,7 @@ class ToOperation:
2134
2238
 
2135
2239
  def __post_init__(self):
2136
2240
  """Post initialization."""
2137
- object.__delattr__(self, "_var_name")
2241
+ object.__delattr__(self, "_js_expr")
2138
2242
 
2139
2243
  def __hash__(self) -> int:
2140
2244
  """Calculate the hash value of the object.
@@ -2173,8 +2277,287 @@ class ToOperation:
2173
2277
  The ToOperation.
2174
2278
  """
2175
2279
  return cls(
2176
- _var_name="", # type: ignore
2280
+ _js_expr="", # type: ignore
2177
2281
  _var_data=_var_data, # type: ignore
2178
2282
  _var_type=_var_type or cls._default_var_type, # type: ignore
2179
2283
  _original=value, # type: ignore
2180
2284
  )
2285
+
2286
+
2287
+ def get_uuid_string_var() -> Var:
2288
+ """Return a Var that generates a single memoized UUID via .web/utils/state.js.
2289
+
2290
+ useMemo with an empty dependency array ensures that the generated UUID is
2291
+ consistent across re-renders of the component.
2292
+
2293
+ Returns:
2294
+ A Var that generates a UUID at runtime.
2295
+ """
2296
+ from reflex.utils.imports import ImportVar
2297
+ from reflex.vars import Var
2298
+
2299
+ unique_uuid_var = get_unique_variable_name()
2300
+ unique_uuid_var_data = VarData(
2301
+ imports={
2302
+ f"/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore
2303
+ "react": "useMemo",
2304
+ },
2305
+ hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
2306
+ )
2307
+
2308
+ return Var(
2309
+ _js_expr=unique_uuid_var,
2310
+ _var_type=str,
2311
+ _var_data=unique_uuid_var_data,
2312
+ )
2313
+
2314
+
2315
+ # Set of unique variable names.
2316
+ USED_VARIABLES = set()
2317
+
2318
+
2319
+ def get_unique_variable_name() -> str:
2320
+ """Get a unique variable name.
2321
+
2322
+ Returns:
2323
+ The unique variable name.
2324
+ """
2325
+ name = "".join([random.choice(string.ascii_lowercase) for _ in range(8)])
2326
+ if name not in USED_VARIABLES:
2327
+ USED_VARIABLES.add(name)
2328
+ return name
2329
+ return get_unique_variable_name()
2330
+
2331
+
2332
+ @dataclasses.dataclass(
2333
+ eq=True,
2334
+ frozen=True,
2335
+ )
2336
+ class VarData:
2337
+ """Metadata associated with a x."""
2338
+
2339
+ # The name of the enclosing state.
2340
+ state: str = dataclasses.field(default="")
2341
+
2342
+ # Imports needed to render this var
2343
+ imports: ImmutableParsedImportDict = dataclasses.field(default_factory=tuple)
2344
+
2345
+ # Hooks that need to be present in the component to render this var
2346
+ hooks: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
2347
+
2348
+ def __init__(
2349
+ self,
2350
+ state: str = "",
2351
+ imports: ImportDict | ParsedImportDict | None = None,
2352
+ hooks: dict[str, None] | None = None,
2353
+ ):
2354
+ """Initialize the var data.
2355
+
2356
+ Args:
2357
+ state: The name of the enclosing state.
2358
+ imports: Imports needed to render this var.
2359
+ hooks: Hooks that need to be present in the component to render this var.
2360
+ """
2361
+ immutable_imports: ImmutableParsedImportDict = tuple(
2362
+ sorted(
2363
+ ((k, tuple(sorted(v))) for k, v in parse_imports(imports or {}).items())
2364
+ )
2365
+ )
2366
+ object.__setattr__(self, "state", state)
2367
+ object.__setattr__(self, "imports", immutable_imports)
2368
+ object.__setattr__(self, "hooks", tuple(hooks or {}))
2369
+
2370
+ def old_school_imports(self) -> ImportDict:
2371
+ """Return the imports as a mutable dict.
2372
+
2373
+ Returns:
2374
+ The imports as a mutable dict.
2375
+ """
2376
+ return dict((k, list(v)) for k, v in self.imports)
2377
+
2378
+ @classmethod
2379
+ def merge(cls, *others: VarData | None) -> VarData | None:
2380
+ """Merge multiple var data objects.
2381
+
2382
+ Args:
2383
+ *others: The var data objects to merge.
2384
+
2385
+ Returns:
2386
+ The merged var data object.
2387
+ """
2388
+ state = ""
2389
+ _imports = {}
2390
+ hooks = {}
2391
+ for var_data in others:
2392
+ if var_data is None:
2393
+ continue
2394
+ state = state or var_data.state
2395
+ _imports = imports.merge_imports(_imports, var_data.imports)
2396
+ hooks.update(
2397
+ var_data.hooks
2398
+ if isinstance(var_data.hooks, dict)
2399
+ else {k: None for k in var_data.hooks}
2400
+ )
2401
+
2402
+ if state or _imports or hooks:
2403
+ return VarData(
2404
+ state=state,
2405
+ imports=_imports,
2406
+ hooks=hooks,
2407
+ )
2408
+ return None
2409
+
2410
+ def __bool__(self) -> bool:
2411
+ """Check if the var data is non-empty.
2412
+
2413
+ Returns:
2414
+ True if any field is set to a non-default value.
2415
+ """
2416
+ return bool(self.state or self.imports or self.hooks)
2417
+
2418
+ def __eq__(self, other: Any) -> bool:
2419
+ """Check if two var data objects are equal.
2420
+
2421
+ Args:
2422
+ other: The other var data object to compare.
2423
+
2424
+ Returns:
2425
+ True if all fields are equal and collapsed imports are equal.
2426
+ """
2427
+ if not isinstance(other, VarData):
2428
+ return False
2429
+
2430
+ # Don't compare interpolations - that's added in by the decoder, and
2431
+ # not part of the vardata itself.
2432
+ return (
2433
+ self.state == other.state
2434
+ and self.hooks
2435
+ == (
2436
+ other.hooks if isinstance(other, VarData) else tuple(other.hooks.keys())
2437
+ )
2438
+ and imports.collapse_imports(self.imports)
2439
+ == imports.collapse_imports(other.imports)
2440
+ )
2441
+
2442
+ @classmethod
2443
+ def from_state(cls, state: Type[BaseState] | str) -> VarData:
2444
+ """Set the state of the var.
2445
+
2446
+ Args:
2447
+ state: The state to set or the full name of the state.
2448
+
2449
+ Returns:
2450
+ The var with the set state.
2451
+ """
2452
+ from reflex.utils import format
2453
+
2454
+ state_name = state if isinstance(state, str) else state.get_full_name()
2455
+ new_var_data = VarData(
2456
+ state=state_name,
2457
+ hooks={
2458
+ "const {0} = useContext(StateContexts.{0})".format(
2459
+ format.format_state_name(state_name)
2460
+ ): None
2461
+ },
2462
+ imports={
2463
+ f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
2464
+ "react": [ImportVar(tag="useContext")],
2465
+ },
2466
+ )
2467
+ return new_var_data
2468
+
2469
+
2470
+ def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
2471
+ """Decode the state name from a formatted var.
2472
+
2473
+ Args:
2474
+ value: The value to extract the state name from.
2475
+
2476
+ Returns:
2477
+ The extracted state name and the value without the state name.
2478
+ """
2479
+ var_datas = []
2480
+ if isinstance(value, str):
2481
+ # fast path if there is no encoded VarData
2482
+ if constants.REFLEX_VAR_OPENING_TAG not in value:
2483
+ return None, value
2484
+
2485
+ offset = 0
2486
+
2487
+ # Find all tags.
2488
+ while m := _decode_var_pattern.search(value):
2489
+ start, end = m.span()
2490
+ value = value[:start] + value[end:]
2491
+
2492
+ serialized_data = m.group(1)
2493
+
2494
+ if serialized_data.isnumeric() or (
2495
+ serialized_data[0] == "-" and serialized_data[1:].isnumeric()
2496
+ ):
2497
+ # This is a global immutable var.
2498
+ var = _global_vars[int(serialized_data)]
2499
+ var_data = var._get_all_var_data()
2500
+
2501
+ if var_data is not None:
2502
+ var_datas.append(var_data)
2503
+ offset += end - start
2504
+
2505
+ return VarData.merge(*var_datas) if var_datas else None, value
2506
+
2507
+
2508
+ # Compile regex for finding reflex var tags.
2509
+ _decode_var_pattern_re = (
2510
+ rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
2511
+ )
2512
+ _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
2513
+
2514
+ # Defined global immutable vars.
2515
+ _global_vars: Dict[int, Var] = {}
2516
+
2517
+
2518
+ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2519
+ """Extract the var imports and hooks from an iterable containing a Var.
2520
+
2521
+ Args:
2522
+ value: The iterable to extract the VarData from
2523
+
2524
+ Returns:
2525
+ The extracted VarDatas.
2526
+ """
2527
+ from reflex.style import Style
2528
+ from reflex.vars import Var
2529
+
2530
+ var_datas = []
2531
+ with contextlib.suppress(TypeError):
2532
+ for sub in value:
2533
+ if isinstance(sub, Var):
2534
+ var_datas.append(sub._var_data)
2535
+ elif not isinstance(sub, str):
2536
+ # Recurse into dict values.
2537
+ if hasattr(sub, "values") and callable(sub.values):
2538
+ var_datas.extend(_extract_var_data(sub.values()))
2539
+ # Recurse into iterable values (or dict keys).
2540
+ var_datas.extend(_extract_var_data(sub))
2541
+
2542
+ # Style objects should already have _var_data.
2543
+ if isinstance(value, Style):
2544
+ var_datas.append(value._var_data)
2545
+ else:
2546
+ # Recurse when value is a dict itself.
2547
+ values = getattr(value, "values", None)
2548
+ if callable(values):
2549
+ var_datas.extend(_extract_var_data(values()))
2550
+ return var_datas
2551
+
2552
+
2553
+ # These names were changed in reflex 0.3.0
2554
+ REPLACED_NAMES = {
2555
+ "full_name": "_var_full_name",
2556
+ "name": "_js_expr",
2557
+ "state": "_var_data.state",
2558
+ "type_": "_var_type",
2559
+ "is_local": "_var_is_local",
2560
+ "is_string": "_var_is_string",
2561
+ "set_state": "_var_set_state",
2562
+ "deps": "_deps",
2563
+ }