reflex 0.5.10a3__py3-none-any.whl → 0.6.0a1__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 (237) hide show
  1. reflex/.templates/jinja/web/pages/utils.js.jinja2 +4 -4
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +1 -1
  3. reflex/.templates/jinja/web/utils/theme.js.jinja2 +1 -1
  4. reflex/__init__.py +3 -2
  5. reflex/__init__.pyi +2 -2
  6. reflex/app.py +43 -9
  7. reflex/base.py +3 -2
  8. reflex/compiler/compiler.py +6 -6
  9. reflex/compiler/utils.py +5 -3
  10. reflex/components/base/app_wrap.py +2 -4
  11. reflex/components/base/app_wrap.pyi +17 -17
  12. reflex/components/base/bare.py +7 -4
  13. reflex/components/base/body.pyi +17 -17
  14. reflex/components/base/document.pyi +81 -81
  15. reflex/components/base/error_boundary.py +10 -8
  16. reflex/components/base/error_boundary.pyi +20 -19
  17. reflex/components/base/fragment.pyi +17 -17
  18. reflex/components/base/head.pyi +33 -33
  19. reflex/components/base/link.pyi +34 -33
  20. reflex/components/base/meta.pyi +65 -65
  21. reflex/components/base/script.py +2 -1
  22. reflex/components/base/script.pyi +21 -20
  23. reflex/components/component.py +116 -145
  24. reflex/components/core/banner.py +59 -60
  25. reflex/components/core/banner.pyi +86 -150
  26. reflex/components/core/client_side_routing.py +2 -1
  27. reflex/components/core/client_side_routing.pyi +34 -33
  28. reflex/components/core/clipboard.py +2 -2
  29. reflex/components/core/clipboard.pyi +19 -18
  30. reflex/components/core/cond.py +21 -44
  31. reflex/components/core/debounce.py +6 -8
  32. reflex/components/core/debounce.pyi +19 -18
  33. reflex/components/core/foreach.py +5 -14
  34. reflex/components/core/html.pyi +18 -17
  35. reflex/components/core/match.py +36 -43
  36. reflex/components/core/upload.py +32 -25
  37. reflex/components/core/upload.pyi +84 -73
  38. reflex/components/datadisplay/code.py +55 -28
  39. reflex/components/datadisplay/code.pyi +20 -17
  40. reflex/components/datadisplay/dataeditor.py +17 -11
  41. reflex/components/datadisplay/dataeditor.pyi +34 -33
  42. reflex/components/el/__init__.py +0 -1
  43. reflex/components/el/__init__.pyi +0 -11
  44. reflex/components/el/element.pyi +17 -17
  45. reflex/components/el/elements/__init__.py +1 -7
  46. reflex/components/el/elements/__init__.pyi +1 -15
  47. reflex/components/el/elements/base.pyi +18 -17
  48. reflex/components/el/elements/forms.py +24 -31
  49. reflex/components/el/elements/forms.pyi +237 -236
  50. reflex/components/el/elements/inline.pyi +450 -449
  51. reflex/components/el/elements/media.py +0 -21
  52. reflex/components/el/elements/media.pyi +338 -337
  53. reflex/components/el/elements/metadata.py +3 -2
  54. reflex/components/el/elements/metadata.pyi +98 -97
  55. reflex/components/el/elements/other.pyi +114 -113
  56. reflex/components/el/elements/scripts.pyi +50 -49
  57. reflex/components/el/elements/sectioning.pyi +242 -241
  58. reflex/components/el/elements/tables.pyi +162 -161
  59. reflex/components/el/elements/typography.pyi +242 -241
  60. reflex/components/gridjs/datatable.py +13 -14
  61. reflex/components/gridjs/datatable.pyi +34 -33
  62. reflex/components/lucide/icon.py +2 -126
  63. reflex/components/lucide/icon.pyi +34 -142
  64. reflex/components/markdown/markdown.py +30 -35
  65. reflex/components/markdown/markdown.pyi +29 -32
  66. reflex/components/moment/moment.pyi +19 -18
  67. reflex/components/next/base.pyi +17 -17
  68. reflex/components/next/image.py +0 -4
  69. reflex/components/next/image.pyi +20 -19
  70. reflex/components/next/link.pyi +18 -17
  71. reflex/components/next/video.pyi +18 -17
  72. reflex/components/plotly/plotly.py +16 -28
  73. reflex/components/plotly/plotly.pyi +36 -35
  74. reflex/components/props.py +21 -10
  75. reflex/components/radix/__init__.pyi +1 -1
  76. reflex/components/radix/primitives/__init__.pyi +0 -1
  77. reflex/components/radix/primitives/accordion.py +7 -8
  78. reflex/components/radix/primitives/accordion.pyi +117 -116
  79. reflex/components/radix/primitives/base.pyi +34 -33
  80. reflex/components/radix/primitives/drawer.pyi +169 -168
  81. reflex/components/radix/primitives/form.pyi +168 -167
  82. reflex/components/radix/primitives/progress.pyi +82 -81
  83. reflex/components/radix/primitives/slider.pyi +84 -83
  84. reflex/components/radix/themes/base.py +8 -4
  85. reflex/components/radix/themes/base.pyi +114 -113
  86. reflex/components/radix/themes/color_mode.py +12 -21
  87. reflex/components/radix/themes/color_mode.pyi +67 -67
  88. reflex/components/radix/themes/components/__init__.pyi +1 -0
  89. reflex/components/radix/themes/components/alert_dialog.pyi +118 -117
  90. reflex/components/radix/themes/components/aspect_ratio.pyi +18 -17
  91. reflex/components/radix/themes/components/avatar.pyi +18 -17
  92. reflex/components/radix/themes/components/badge.pyi +18 -17
  93. reflex/components/radix/themes/components/button.pyi +18 -17
  94. reflex/components/radix/themes/components/callout.pyi +82 -81
  95. reflex/components/radix/themes/components/card.pyi +18 -17
  96. reflex/components/radix/themes/components/checkbox.py +2 -3
  97. reflex/components/radix/themes/components/checkbox.pyi +53 -52
  98. reflex/components/radix/themes/components/checkbox_cards.pyi +34 -33
  99. reflex/components/radix/themes/components/checkbox_group.pyi +34 -33
  100. reflex/components/radix/themes/components/context_menu.pyi +140 -139
  101. reflex/components/radix/themes/components/data_list.py +5 -0
  102. reflex/components/radix/themes/components/data_list.pyi +71 -65
  103. reflex/components/radix/themes/components/dialog.pyi +121 -120
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +142 -141
  105. reflex/components/radix/themes/components/hover_card.pyi +68 -67
  106. reflex/components/radix/themes/components/icon_button.py +2 -1
  107. reflex/components/radix/themes/components/icon_button.pyi +18 -17
  108. reflex/components/radix/themes/components/inset.pyi +18 -17
  109. reflex/components/radix/themes/components/popover.pyi +73 -72
  110. reflex/components/radix/themes/components/progress.pyi +18 -17
  111. reflex/components/radix/themes/components/radio.pyi +18 -17
  112. reflex/components/radix/themes/components/radio_cards.pyi +35 -34
  113. reflex/components/radix/themes/components/radio_group.py +35 -31
  114. reflex/components/radix/themes/components/radio_group.pyi +73 -66
  115. reflex/components/radix/themes/components/scroll_area.pyi +18 -17
  116. reflex/components/radix/themes/components/segmented_control.pyi +35 -34
  117. reflex/components/radix/themes/components/select.py +2 -1
  118. reflex/components/radix/themes/components/select.pyi +155 -154
  119. reflex/components/radix/themes/components/separator.py +2 -3
  120. reflex/components/radix/themes/components/separator.pyi +18 -17
  121. reflex/components/radix/themes/components/skeleton.pyi +18 -17
  122. reflex/components/radix/themes/components/slider.py +2 -1
  123. reflex/components/radix/themes/components/slider.pyi +20 -19
  124. reflex/components/radix/themes/components/spinner.pyi +18 -17
  125. reflex/components/radix/themes/components/switch.pyi +19 -18
  126. reflex/components/radix/themes/components/table.pyi +114 -113
  127. reflex/components/radix/themes/components/tabs.pyi +84 -83
  128. reflex/components/radix/themes/components/text_area.pyi +21 -20
  129. reflex/components/radix/themes/components/text_field.py +0 -79
  130. reflex/components/radix/themes/components/text_field.pyi +57 -63
  131. reflex/components/radix/themes/components/tooltip.pyi +21 -20
  132. reflex/components/radix/themes/layout/base.pyi +18 -17
  133. reflex/components/radix/themes/layout/box.pyi +18 -17
  134. reflex/components/radix/themes/layout/center.pyi +18 -17
  135. reflex/components/radix/themes/layout/container.py +2 -3
  136. reflex/components/radix/themes/layout/container.pyi +18 -17
  137. reflex/components/radix/themes/layout/flex.pyi +18 -17
  138. reflex/components/radix/themes/layout/grid.pyi +18 -17
  139. reflex/components/radix/themes/layout/list.py +5 -4
  140. reflex/components/radix/themes/layout/list.pyi +86 -85
  141. reflex/components/radix/themes/layout/section.py +2 -3
  142. reflex/components/radix/themes/layout/section.pyi +18 -17
  143. reflex/components/radix/themes/layout/spacer.pyi +18 -17
  144. reflex/components/radix/themes/layout/stack.pyi +50 -49
  145. reflex/components/radix/themes/typography/blockquote.pyi +18 -17
  146. reflex/components/radix/themes/typography/code.pyi +18 -17
  147. reflex/components/radix/themes/typography/heading.pyi +18 -17
  148. reflex/components/radix/themes/typography/link.pyi +18 -17
  149. reflex/components/radix/themes/typography/text.pyi +114 -113
  150. reflex/components/react_player/audio.pyi +34 -33
  151. reflex/components/react_player/react_player.pyi +34 -33
  152. reflex/components/react_player/video.pyi +34 -33
  153. reflex/components/recharts/cartesian.py +23 -19
  154. reflex/components/recharts/cartesian.pyi +297 -296
  155. reflex/components/recharts/charts.py +6 -5
  156. reflex/components/recharts/charts.pyi +179 -178
  157. reflex/components/recharts/general.py +8 -7
  158. reflex/components/recharts/general.pyi +82 -81
  159. reflex/components/recharts/polar.py +14 -13
  160. reflex/components/recharts/polar.pyi +76 -75
  161. reflex/components/recharts/recharts.pyi +33 -33
  162. reflex/components/sonner/toast.py +30 -33
  163. reflex/components/sonner/toast.pyi +27 -25
  164. reflex/components/suneditor/editor.py +2 -1
  165. reflex/components/suneditor/editor.pyi +27 -26
  166. reflex/components/tags/iter_tag.py +16 -16
  167. reflex/components/tags/tag.py +8 -10
  168. reflex/constants/base.py +3 -1
  169. reflex/constants/event.py +1 -0
  170. reflex/event.py +89 -79
  171. reflex/experimental/__init__.py +25 -6
  172. reflex/experimental/client_state.py +34 -58
  173. reflex/experimental/hooks.py +13 -18
  174. reflex/experimental/layout.py +5 -5
  175. reflex/experimental/layout.pyi +84 -83
  176. reflex/{experimental/vars → ivars}/__init__.py +0 -1
  177. reflex/ivars/base.py +2180 -0
  178. reflex/ivars/function.py +200 -0
  179. reflex/ivars/number.py +1137 -0
  180. reflex/ivars/object.py +564 -0
  181. reflex/ivars/sequence.py +1601 -0
  182. reflex/model.py +22 -0
  183. reflex/reflex.py +4 -0
  184. reflex/state.py +388 -73
  185. reflex/style.py +52 -34
  186. reflex/testing.py +8 -3
  187. reflex/utils/exceptions.py +12 -0
  188. reflex/utils/exec.py +0 -14
  189. reflex/utils/format.py +74 -223
  190. reflex/utils/net.py +43 -0
  191. reflex/utils/path_ops.py +13 -1
  192. reflex/utils/prerequisites.py +46 -26
  193. reflex/utils/pyi_generator.py +5 -4
  194. reflex/utils/serializers.py +13 -31
  195. reflex/utils/types.py +44 -9
  196. reflex/vars.py +127 -2230
  197. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/METADATA +4 -6
  198. reflex-0.6.0a1.dist-info/RECORD +384 -0
  199. reflex/.templates/apps/demo/.gitignore +0 -4
  200. reflex/.templates/apps/demo/assets/favicon.ico +0 -0
  201. reflex/.templates/apps/demo/assets/github.svg +0 -10
  202. reflex/.templates/apps/demo/assets/icon.svg +0 -37
  203. reflex/.templates/apps/demo/assets/logo.svg +0 -68
  204. reflex/.templates/apps/demo/assets/paneleft.svg +0 -13
  205. reflex/.templates/apps/demo/code/__init__.py +0 -1
  206. reflex/.templates/apps/demo/code/demo.py +0 -127
  207. reflex/.templates/apps/demo/code/pages/__init__.py +0 -7
  208. reflex/.templates/apps/demo/code/pages/chatapp.py +0 -31
  209. reflex/.templates/apps/demo/code/pages/datatable.py +0 -360
  210. reflex/.templates/apps/demo/code/pages/forms.py +0 -257
  211. reflex/.templates/apps/demo/code/pages/graphing.py +0 -253
  212. reflex/.templates/apps/demo/code/pages/home.py +0 -56
  213. reflex/.templates/apps/demo/code/sidebar.py +0 -178
  214. reflex/.templates/apps/demo/code/state.py +0 -22
  215. reflex/.templates/apps/demo/code/states/form_state.py +0 -40
  216. reflex/.templates/apps/demo/code/states/pie_state.py +0 -47
  217. reflex/.templates/apps/demo/code/styles.py +0 -68
  218. reflex/.templates/apps/demo/code/webui/__init__.py +0 -0
  219. reflex/.templates/apps/demo/code/webui/components/__init__.py +0 -4
  220. reflex/.templates/apps/demo/code/webui/components/chat.py +0 -118
  221. reflex/.templates/apps/demo/code/webui/components/loading_icon.py +0 -19
  222. reflex/.templates/apps/demo/code/webui/components/modal.py +0 -56
  223. reflex/.templates/apps/demo/code/webui/components/navbar.py +0 -70
  224. reflex/.templates/apps/demo/code/webui/components/sidebar.py +0 -66
  225. reflex/.templates/apps/demo/code/webui/state.py +0 -146
  226. reflex/.templates/apps/demo/code/webui/styles.py +0 -88
  227. reflex/experimental/vars/base.py +0 -583
  228. reflex/experimental/vars/function.py +0 -290
  229. reflex/experimental/vars/number.py +0 -1458
  230. reflex/experimental/vars/object.py +0 -804
  231. reflex/experimental/vars/sequence.py +0 -1764
  232. reflex/utils/watch.py +0 -96
  233. reflex/vars.pyi +0 -218
  234. reflex-0.5.10a3.dist-info/RECORD +0 -413
  235. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/LICENSE +0 -0
  236. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/WHEEL +0 -0
  237. {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/entry_points.txt +0 -0
reflex/vars.py CHANGED
@@ -4,43 +4,24 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import dataclasses
7
- import datetime
8
- import dis
9
- import functools
10
- import inspect
11
- import json
12
7
  import random
13
8
  import re
14
9
  import string
15
- import sys
16
- from types import CodeType, FunctionType
17
10
  from typing import (
18
11
  TYPE_CHECKING,
19
12
  Any,
20
- Callable,
21
13
  Dict,
22
14
  Iterable,
23
- List,
24
- Literal,
25
15
  Optional,
26
16
  Tuple,
27
17
  Type,
28
- Union,
29
18
  _GenericAlias, # type: ignore
30
- cast,
31
- get_args,
32
- get_origin,
33
- get_type_hints,
34
19
  )
35
20
 
36
21
  from reflex import constants
37
- from reflex.base import Base
38
- from reflex.utils import console, imports, serializers, types
22
+ from reflex.utils import imports
39
23
  from reflex.utils.exceptions import (
40
- VarAttributeError,
41
- VarDependencyError,
42
24
  VarTypeError,
43
- VarValueError,
44
25
  )
45
26
 
46
27
  # This module used to export ImportVar itself, so we still import it for export here
@@ -51,43 +32,15 @@ from reflex.utils.imports import (
51
32
  ParsedImportDict,
52
33
  parse_imports,
53
34
  )
54
- from reflex.utils.types import override
55
35
 
56
36
  if TYPE_CHECKING:
37
+ from reflex.ivars import ImmutableVar
57
38
  from reflex.state import BaseState
58
39
 
59
40
 
60
41
  # Set of unique variable names.
61
42
  USED_VARIABLES = set()
62
43
 
63
- # Supported operators for all types.
64
- ALL_OPS = ["==", "!=", "!==", "===", "&&", "||"]
65
- # Delimiters used between function args or operands.
66
- DELIMITERS = [","]
67
- # Mapping of valid operations for different type combinations.
68
- OPERATION_MAPPING = {
69
- (int, int): {
70
- "+",
71
- "-",
72
- "/",
73
- "//",
74
- "*",
75
- "%",
76
- "**",
77
- ">",
78
- "<",
79
- "<=",
80
- ">=",
81
- "|",
82
- "&",
83
- },
84
- (int, str): {"*"},
85
- (int, list): {"*"},
86
- (str, str): {"+", ">", "<", "<=", ">="},
87
- (float, float): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
88
- (float, int): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
89
- (list, list): {"+", ">", "<", "<=", ">="},
90
- }
91
44
 
92
45
  # These names were changed in reflex 0.3.0
93
46
  REPLACED_NAMES = {
@@ -101,15 +54,6 @@ REPLACED_NAMES = {
101
54
  "deps": "_deps",
102
55
  }
103
56
 
104
- PYTHON_JS_TYPE_MAP = {
105
- (int, float): "number",
106
- (str,): "string",
107
- (bool,): "boolean",
108
- (list, tuple): "Array",
109
- (dict,): "Object",
110
- (None,): "null",
111
- }
112
-
113
57
 
114
58
  def get_unique_variable_name() -> str:
115
59
  """Get a unique variable name.
@@ -124,125 +68,11 @@ def get_unique_variable_name() -> str:
124
68
  return get_unique_variable_name()
125
69
 
126
70
 
127
- class VarData(Base):
128
- """Metadata associated with a Var."""
129
-
130
- # The name of the enclosing state.
131
- state: str = ""
132
-
133
- # Imports needed to render this var
134
- imports: ParsedImportDict = {}
135
-
136
- # Hooks that need to be present in the component to render this var
137
- hooks: Dict[str, None] = {}
138
-
139
- # Positions of interpolated strings. This is used by the decoder to figure
140
- # out where the interpolations are and only escape the non-interpolated
141
- # segments.
142
- interpolations: List[Tuple[int, int]] = []
143
-
144
- def __init__(
145
- self, imports: Union[ImportDict, ParsedImportDict] | None = None, **kwargs: Any
146
- ):
147
- """Initialize the var data.
148
-
149
- Args:
150
- imports: The imports needed to render this var.
151
- **kwargs: The var data fields.
152
- """
153
- if imports:
154
- kwargs["imports"] = parse_imports(imports)
155
- super().__init__(**kwargs)
156
-
157
- @classmethod
158
- def merge(cls, *others: ImmutableVarData | VarData | None) -> VarData | None:
159
- """Merge multiple var data objects.
160
-
161
- Args:
162
- *others: The var data objects to merge.
163
-
164
- Returns:
165
- The merged var data object.
166
- """
167
- state = ""
168
- _imports = {}
169
- hooks = {}
170
- interpolations = []
171
- for var_data in others:
172
- if var_data is None:
173
- continue
174
- state = state or var_data.state
175
- _imports = imports.merge_imports(_imports, var_data.imports)
176
- hooks.update(
177
- var_data.hooks
178
- if isinstance(var_data.hooks, dict)
179
- else {k: None for k in var_data.hooks}
180
- )
181
- interpolations += (
182
- var_data.interpolations if isinstance(var_data, VarData) else []
183
- )
184
-
185
- return (
186
- cls(
187
- state=state,
188
- imports=_imports,
189
- hooks=hooks,
190
- interpolations=interpolations,
191
- )
192
- or None
193
- )
194
-
195
- def __bool__(self) -> bool:
196
- """Check if the var data is non-empty.
197
-
198
- Returns:
199
- True if any field is set to a non-default value.
200
- """
201
- return bool(self.state or self.imports or self.hooks or self.interpolations)
202
-
203
- def __eq__(self, other: Any) -> bool:
204
- """Check if two var data objects are equal.
205
-
206
- Args:
207
- other: The other var data object to compare.
208
-
209
- Returns:
210
- True if all fields are equal and collapsed imports are equal.
211
- """
212
- if not isinstance(other, VarData):
213
- return False
214
-
215
- # Don't compare interpolations - that's added in by the decoder, and
216
- # not part of the vardata itself.
217
- return (
218
- self.state == other.state
219
- and self.hooks.keys() == other.hooks.keys()
220
- and imports.collapse_imports(self.imports)
221
- == imports.collapse_imports(other.imports)
222
- )
223
-
224
- def dict(self) -> dict:
225
- """Convert the var data to a dictionary.
226
-
227
- Returns:
228
- The var data dictionary.
229
- """
230
- return {
231
- "state": self.state,
232
- "interpolations": list(self.interpolations),
233
- "imports": {
234
- lib: [import_var.dict() for import_var in import_vars]
235
- for lib, import_vars in self.imports.items()
236
- },
237
- "hooks": self.hooks,
238
- }
239
-
240
-
241
71
  @dataclasses.dataclass(
242
72
  eq=True,
243
73
  frozen=True,
244
74
  )
245
- class ImmutableVarData:
75
+ class VarData:
246
76
  """Metadata associated with a Var."""
247
77
 
248
78
  # The name of the enclosing state.
@@ -276,10 +106,16 @@ class ImmutableVarData:
276
106
  object.__setattr__(self, "imports", immutable_imports)
277
107
  object.__setattr__(self, "hooks", tuple(hooks or {}))
278
108
 
109
+ def old_school_imports(self) -> ImportDict:
110
+ """Return the imports as a mutable dict.
111
+
112
+ Returns:
113
+ The imports as a mutable dict.
114
+ """
115
+ return dict((k, list(v)) for k, v in self.imports)
116
+
279
117
  @classmethod
280
- def merge(
281
- cls, *others: ImmutableVarData | VarData | None
282
- ) -> ImmutableVarData | None:
118
+ def merge(cls, *others: VarData | None) -> VarData | None:
283
119
  """Merge multiple var data objects.
284
120
 
285
121
  Args:
@@ -302,14 +138,13 @@ class ImmutableVarData:
302
138
  else {k: None for k in var_data.hooks}
303
139
  )
304
140
 
305
- return (
306
- ImmutableVarData(
141
+ if state or _imports or hooks:
142
+ return VarData(
307
143
  state=state,
308
144
  imports=_imports,
309
145
  hooks=hooks,
310
146
  )
311
- or None
312
- )
147
+ return None
313
148
 
314
149
  def __bool__(self) -> bool:
315
150
  """Check if the var data is non-empty.
@@ -328,7 +163,7 @@ class ImmutableVarData:
328
163
  Returns:
329
164
  True if all fields are equal and collapsed imports are equal.
330
165
  """
331
- if not isinstance(other, (ImmutableVarData, VarData)):
166
+ if not isinstance(other, VarData):
332
167
  return False
333
168
 
334
169
  # Don't compare interpolations - that's added in by the decoder, and
@@ -337,16 +172,41 @@ class ImmutableVarData:
337
172
  self.state == other.state
338
173
  and self.hooks
339
174
  == (
340
- other.hooks
341
- if isinstance(other, ImmutableVarData)
342
- else tuple(other.hooks.keys())
175
+ other.hooks if isinstance(other, VarData) else tuple(other.hooks.keys())
343
176
  )
344
177
  and imports.collapse_imports(self.imports)
345
178
  == imports.collapse_imports(other.imports)
346
179
  )
347
180
 
181
+ @classmethod
182
+ def from_state(cls, state: Type[BaseState] | str) -> VarData:
183
+ """Set the state of the var.
184
+
185
+ Args:
186
+ state: The state to set or the full name of the state.
187
+
188
+ Returns:
189
+ The var with the set state.
190
+ """
191
+ from reflex.utils import format
192
+
193
+ state_name = state if isinstance(state, str) else state.get_full_name()
194
+ new_var_data = VarData(
195
+ state=state_name,
196
+ hooks={
197
+ "const {0} = useContext(StateContexts.{0})".format(
198
+ format.format_state_name(state_name)
199
+ ): None
200
+ },
201
+ imports={
202
+ f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
203
+ "react": [ImportVar(tag="useContext")],
204
+ },
205
+ )
206
+ return new_var_data
207
+
348
208
 
349
- def _decode_var_immutable(value: str) -> tuple[ImmutableVarData | None, str]:
209
+ def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
350
210
  """Decode the state name from a formatted var.
351
211
 
352
212
  Args:
@@ -363,15 +223,6 @@ def _decode_var_immutable(value: str) -> tuple[ImmutableVarData | None, str]:
363
223
 
364
224
  offset = 0
365
225
 
366
- # Initialize some methods for reading json.
367
- var_data_config = VarData().__config__
368
-
369
- def json_loads(s):
370
- try:
371
- return var_data_config.json_loads(s)
372
- except json.decoder.JSONDecodeError:
373
- return var_data_config.json_loads(var_data_config.json_loads(f'"{s}"'))
374
-
375
226
  # Find all tags.
376
227
  while m := _decode_var_pattern.search(value):
377
228
  start, end = m.span()
@@ -384,52 +235,13 @@ def _decode_var_immutable(value: str) -> tuple[ImmutableVarData | None, str]:
384
235
  ):
385
236
  # This is a global immutable var.
386
237
  var = _global_vars[int(serialized_data)]
387
- var_data = var._var_data
238
+ var_data = var._get_all_var_data()
388
239
 
389
240
  if var_data is not None:
390
- realstart = start + offset
391
-
392
241
  var_datas.append(var_data)
393
- else:
394
- # Read the JSON, pull out the string length, parse the rest as VarData.
395
- data = json_loads(serialized_data)
396
- string_length = data.pop("string_length", None)
397
- var_data = VarData.parse_obj(data)
398
-
399
- # Use string length to compute positions of interpolations.
400
- if string_length is not None:
401
- realstart = start + offset
402
- var_data.interpolations = [(realstart, realstart + string_length)]
403
-
404
- var_datas.append(var_data)
405
242
  offset += end - start
406
243
 
407
- return ImmutableVarData.merge(*var_datas) if var_datas else None, value
408
-
409
-
410
- def _encode_var(value: Var) -> str:
411
- """Encode the state name into a formatted var.
412
-
413
- Args:
414
- value: The value to encode the state name into.
415
-
416
- Returns:
417
- The encoded var.
418
- """
419
- if value._var_data:
420
- from reflex.utils.serializers import serialize
421
-
422
- final_value = str(value)
423
- data = value._var_data.dict()
424
- data["string_length"] = len(final_value)
425
- data_json = value._var_data.__config__.json_dumps(data, default=serialize)
426
-
427
- return (
428
- f"{constants.REFLEX_VAR_OPENING_TAG}{data_json}{constants.REFLEX_VAR_CLOSING_TAG}"
429
- + final_value
430
- )
431
-
432
- return str(value)
244
+ return VarData.merge(*var_datas) if var_datas else None, value
433
245
 
434
246
 
435
247
  # Compile regex for finding reflex var tags.
@@ -439,68 +251,7 @@ _decode_var_pattern_re = (
439
251
  _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
440
252
 
441
253
  # Defined global immutable vars.
442
- _global_vars: Dict[int, Var] = {}
443
-
444
-
445
- def _decode_var(value: str) -> tuple[VarData | None, str]:
446
- """Decode the state name from a formatted var.
447
-
448
- Args:
449
- value: The value to extract the state name from.
450
-
451
- Returns:
452
- The extracted state name and the value without the state name.
453
- """
454
- var_datas = []
455
- if isinstance(value, str):
456
- # fast path if there is no encoded VarData
457
- if constants.REFLEX_VAR_OPENING_TAG not in value:
458
- return None, value
459
-
460
- offset = 0
461
-
462
- # Initialize some methods for reading json.
463
- var_data_config = VarData().__config__
464
-
465
- def json_loads(s):
466
- try:
467
- return var_data_config.json_loads(s)
468
- except json.decoder.JSONDecodeError:
469
- return var_data_config.json_loads(var_data_config.json_loads(f'"{s}"'))
470
-
471
- # Find all tags.
472
- while m := _decode_var_pattern.search(value):
473
- start, end = m.span()
474
- value = value[:start] + value[end:]
475
-
476
- serialized_data = m.group(1)
477
-
478
- if serialized_data.isnumeric() or (
479
- serialized_data[0] == "-" and serialized_data[1:].isnumeric()
480
- ):
481
- # This is a global immutable var.
482
- var = _global_vars[int(serialized_data)]
483
- var_data = var._var_data
484
-
485
- if var_data is not None:
486
- realstart = start + offset
487
-
488
- var_datas.append(var_data)
489
- else:
490
- # Read the JSON, pull out the string length, parse the rest as VarData.
491
- data = json_loads(serialized_data)
492
- string_length = data.pop("string_length", None)
493
- var_data = VarData.parse_obj(data)
494
-
495
- # Use string length to compute positions of interpolations.
496
- if string_length is not None:
497
- realstart = start + offset
498
- var_data.interpolations = [(realstart, realstart + string_length)]
499
-
500
- var_datas.append(var_data)
501
- offset += end - start
502
-
503
- return VarData.merge(*var_datas) if var_datas else None, value
254
+ _global_vars: Dict[int, ImmutableVar] = {}
504
255
 
505
256
 
506
257
  def _extract_var_data(value: Iterable) -> list[VarData | None]:
@@ -512,12 +263,13 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
512
263
  Returns:
513
264
  The extracted VarDatas.
514
265
  """
266
+ from reflex.ivars import ImmutableVar
515
267
  from reflex.style import Style
516
268
 
517
269
  var_datas = []
518
270
  with contextlib.suppress(TypeError):
519
271
  for sub in value:
520
- if isinstance(sub, Var):
272
+ if isinstance(sub, ImmutableVar):
521
273
  var_datas.append(sub._var_data)
522
274
  elif not isinstance(sub, str):
523
275
  # Recurse into dict values.
@@ -540,24 +292,6 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
540
292
  class Var:
541
293
  """An abstract var."""
542
294
 
543
- # The name of the var.
544
- _var_name: str
545
-
546
- # The type of the var.
547
- _var_type: Type
548
-
549
- # Whether this is a local javascript variable.
550
- _var_is_local: bool
551
-
552
- # Whether the var is a string literal.
553
- _var_is_string: bool
554
-
555
- # _var_full_name should be prefixed with _var_state
556
- _var_full_name_needs_state_prefix: bool
557
-
558
- # Extra metadata associated with the Var
559
- _var_data: Optional[VarData]
560
-
561
295
  @classmethod
562
296
  def create(
563
297
  cls,
@@ -565,7 +299,7 @@ class Var:
565
299
  _var_is_local: bool = True,
566
300
  _var_is_string: bool | None = None,
567
301
  _var_data: Optional[VarData] = None,
568
- ) -> Var | None:
302
+ ) -> ImmutableVar | None:
569
303
  """Create a var from a value.
570
304
 
571
305
  Args:
@@ -576,59 +310,45 @@ class Var:
576
310
 
577
311
  Returns:
578
312
  The var.
579
-
580
- Raises:
581
- VarTypeError: If the value is JSON-unserializable.
582
313
  """
583
- from reflex.utils import format
314
+ from reflex.ivars import ImmutableVar, LiteralVar
584
315
 
585
316
  # Check for none values.
586
317
  if value is None:
587
318
  return None
588
319
 
589
320
  # If the value is already a var, do nothing.
590
- if isinstance(value, Var):
321
+ if isinstance(value, ImmutableVar):
591
322
  return value
592
323
 
593
- # Try to pull the imports and hooks from contained values.
324
+ # If the value is not a string, create a LiteralVar.
594
325
  if not isinstance(value, str):
595
- _var_data = VarData.merge(*_extract_var_data(value), _var_data)
596
-
597
- # Try to serialize the value.
598
- type_ = type(value)
599
- if type_ in types.JSONType:
600
- name = value
601
- else:
602
- name, serialized_type = serializers.serialize(value, get_type=True)
603
- if (
604
- serialized_type is not None
605
- and _var_is_string is None
606
- and issubclass(serialized_type, str)
607
- ):
608
- _var_is_string = True
609
- if name is None:
610
- raise VarTypeError(
611
- f"No JSON serializer found for var {value} of type {type_}."
326
+ return LiteralVar.create(
327
+ value,
328
+ _var_data=_var_data,
612
329
  )
613
- name = name if isinstance(name, str) else format.json_dumps(name)
614
-
615
- if _var_is_string is None and type_ is str:
616
- console.deprecate(
617
- feature_name=f"Creating a Var ({value}) from a string without specifying _var_is_string",
618
- reason=(
619
- "Specify _var_is_string=False to create a Var that is not a string literal. "
620
- "In the future, creating a Var from a string will be treated as a string literal "
621
- "by default."
622
- ),
623
- deprecation_version="0.5.4",
624
- removal_version="0.6.0",
330
+
331
+ # if _var_is_string is provided, use that
332
+ if _var_is_string is False:
333
+ return ImmutableVar.create(
334
+ value,
335
+ _var_data=_var_data,
336
+ )
337
+ if _var_is_string is True:
338
+ return LiteralVar.create(
339
+ value,
340
+ _var_data=_var_data,
625
341
  )
626
342
 
627
- return BaseVar(
628
- _var_name=name,
629
- _var_type=type_,
630
- _var_is_local=_var_is_local,
631
- _var_is_string=_var_is_string if _var_is_string is not None else False,
343
+ # Otherwise, rely on _var_is_local
344
+ if _var_is_local is True:
345
+ return LiteralVar.create(
346
+ value,
347
+ _var_data=_var_data,
348
+ )
349
+
350
+ return ImmutableVar.create(
351
+ value,
632
352
  _var_data=_var_data,
633
353
  )
634
354
 
@@ -639,7 +359,7 @@ class Var:
639
359
  _var_is_local: bool = True,
640
360
  _var_is_string: bool | None = None,
641
361
  _var_data: Optional[VarData] = None,
642
- ) -> Var:
362
+ ) -> ImmutableVar:
643
363
  """Create a var from a value, asserting that it is not None.
644
364
 
645
365
  Args:
@@ -672,1912 +392,88 @@ class Var:
672
392
  """
673
393
  return _GenericAlias(cls, type_)
674
394
 
675
- def __post_init__(self) -> None:
676
- """Post-initialize the var."""
677
- # Decode any inline Var markup and apply it to the instance
678
- _var_data, _var_name = _decode_var(self._var_name)
679
- if _var_data:
680
- self._var_name = _var_name
681
- self._var_data = VarData.merge(self._var_data, _var_data)
682
-
683
- def _replace(self, merge_var_data=None, **kwargs: Any) -> BaseVar:
684
- """Make a copy of this Var with updated fields.
685
-
686
- Args:
687
- merge_var_data: VarData to merge into the existing VarData.
688
- **kwargs: Var fields to update.
689
-
690
- Returns:
691
- A new BaseVar with the updated fields overwriting the corresponding fields in this Var.
395
+ def __bool__(self) -> bool:
396
+ """Raise exception if using Var in a boolean context.
692
397
 
693
398
  Raises:
694
- TypeError: If kwargs contains keys that are not allowed.
399
+ VarTypeError: when attempting to bool-ify the Var.
695
400
  """
696
- field_values = dict(
697
- _var_name=kwargs.pop("_var_name", self._var_name),
698
- _var_type=kwargs.pop("_var_type", self._var_type),
699
- _var_is_local=kwargs.pop("_var_is_local", self._var_is_local),
700
- _var_is_string=kwargs.pop("_var_is_string", self._var_is_string),
701
- _var_full_name_needs_state_prefix=kwargs.pop(
702
- "_var_full_name_needs_state_prefix",
703
- self._var_full_name_needs_state_prefix,
704
- ),
705
- _var_data=VarData.merge(
706
- kwargs.pop("_var_data", self._var_data), merge_var_data
707
- ),
401
+ raise VarTypeError(
402
+ f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
403
+ "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
708
404
  )
709
405
 
710
- if kwargs:
711
- unexpected_kwargs = ", ".join(kwargs.keys())
712
- raise TypeError(f"Unexpected keyword arguments: {unexpected_kwargs}")
713
-
714
- return BaseVar(**field_values)
715
-
716
- def _decode(self) -> Any:
717
- """Decode Var as a python value.
718
-
719
- Note that Var with state set cannot be decoded python-side and will be
720
- returned as full_name.
406
+ def __iter__(self) -> Any:
407
+ """Raise exception if using Var in an iterable context.
721
408
 
722
- Returns:
723
- The decoded value or the Var name.
409
+ Raises:
410
+ VarTypeError: when attempting to iterate over the Var.
724
411
  """
725
- if self._var_is_string:
726
- return self._var_name
727
- try:
728
- return json.loads(self._var_name)
729
- except ValueError:
730
- return self._var_name
731
-
732
- def equals(self, other: Var) -> bool:
733
- """Check if two vars are equal.
412
+ raise VarTypeError(
413
+ f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
414
+ )
734
415
 
735
- Args:
736
- other: The other var to compare.
416
+ def __contains__(self, _: Any) -> ImmutableVar:
417
+ """Override the 'in' operator to alert the user that it is not supported.
737
418
 
738
- Returns:
739
- Whether the vars are equal.
419
+ Raises:
420
+ VarTypeError: the operation is not supported
740
421
  """
741
- return (
742
- self._var_name == other._var_name
743
- and self._var_type == other._var_type
744
- and self._var_is_local == other._var_is_local
745
- and self._var_full_name_needs_state_prefix
746
- == other._var_full_name_needs_state_prefix
747
- and self._var_data == other._var_data
422
+ raise VarTypeError(
423
+ "'in' operator not supported for Var types, use Var.contains() instead."
748
424
  )
749
425
 
750
- def _merge(self, other) -> Var:
751
- """Merge two or more dicts.
426
+ def __get__(self, instance: Any, owner: Any) -> ImmutableVar:
427
+ """Return the value of the Var.
752
428
 
753
429
  Args:
754
- other: The other var to merge.
430
+ instance: The instance of the Var.
431
+ owner: The owner of the Var.
755
432
 
756
433
  Returns:
757
- The merged var.
434
+ The value of the Var.
758
435
  """
759
- if other is None:
760
- return self._replace()
761
- if not isinstance(other, Var):
762
- other = Var.create(other, _var_is_string=False)
763
- return self._replace(
764
- _var_name=f"{{...{self._var_name}, ...{other._var_name}}}" # type: ignore
765
- )
436
+ return self # type: ignore
766
437
 
767
- def to_string(self, json: bool = True) -> Var:
768
- """Convert a var to a string.
438
+ @classmethod
439
+ def range(
440
+ cls,
441
+ v1: ImmutableVar | int = 0,
442
+ v2: ImmutableVar | int | None = None,
443
+ step: ImmutableVar | int | None = None,
444
+ ) -> ImmutableVar:
445
+ """Return an iterator over indices from v1 to v2 (or 0 to v1).
769
446
 
770
447
  Args:
771
- json: Whether to convert to a JSON string.
772
-
773
- Returns:
774
- The stringified var.
775
- """
776
- fn = "JSON.stringify" if json else "String"
777
- return self.operation(fn=fn, type_=str)
778
-
779
- def to_int(self) -> Var:
780
- """Convert a var to an int.
448
+ v1: The start of the range or end of range if v2 is not given.
449
+ v2: The end of the range.
450
+ step: The number of numbers between each item.
781
451
 
782
452
  Returns:
783
- The parseInt var.
453
+ A var representing range operation.
784
454
  """
785
- return self.operation(fn="parseInt", type_=int)
455
+ from reflex.ivars import ArrayVar
786
456
 
787
- def __hash__(self) -> int:
788
- """Define a hash function for a var.
457
+ return ArrayVar.range(v1, v2, step) # type: ignore
789
458
 
790
- Returns:
791
- The hash of the var.
792
- """
793
- return hash((self._var_name, str(self._var_type)))
794
-
795
- def __str__(self) -> str:
796
- """Wrap the var so it can be used in templates.
459
+ def upcast(self) -> ImmutableVar:
460
+ """Upcast a Var to an ImmutableVar.
797
461
 
798
462
  Returns:
799
- The wrapped var, i.e. {state.var}.
800
- """
801
- from reflex.utils import format
802
-
803
- out = (
804
- self._var_full_name
805
- if self._var_is_local
806
- else format.wrap(self._var_full_name, "{")
807
- )
808
- if self._var_is_string:
809
- out = format.format_string(out)
810
- return out
811
-
812
- def __bool__(self) -> bool:
813
- """Raise exception if using Var in a boolean context.
814
-
815
- Raises:
816
- VarTypeError: when attempting to bool-ify the Var.
463
+ The upcasted Var.
817
464
  """
818
- raise VarTypeError(
819
- f"Cannot convert Var {self._var_full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
820
- "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
821
- )
465
+ return self # type: ignore
822
466
 
823
- def __iter__(self) -> Any:
824
- """Raise exception if using Var in an iterable context.
467
+ def json(self) -> str:
468
+ """Serialize the var to a JSON string.
825
469
 
826
470
  Raises:
827
- VarTypeError: when attempting to iterate over the Var.
828
- """
829
- raise VarTypeError(
830
- f"Cannot iterate over Var {self._var_full_name!r}. Instead use `rx.foreach`."
831
- )
832
-
833
- def __format__(self, format_spec: str) -> str:
834
- """Format the var into a Javascript equivalent to an f-string.
835
-
836
- Args:
837
- format_spec: The format specifier (Ignored for now).
838
-
839
- Returns:
840
- The formatted var.
841
- """
842
- # Encode the _var_data into the formatted output for tracking purposes.
843
- str_self = _encode_var(self)
844
- if self._var_is_local:
845
- return str_self
846
- return f"${str_self}"
847
-
848
- def __getitem__(self, i: Any) -> Var:
849
- """Index into a var.
850
-
851
- Args:
852
- i: The index to index into.
853
-
854
- Returns:
855
- The indexed var.
856
-
857
- Raises:
858
- VarTypeError: If the var is not indexable.
859
- """
860
- from reflex.utils import format
861
-
862
- # Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
863
- if not (
864
- types._issubclass(self._var_type, Union[List, Dict, Tuple, str])
865
- or types.is_dataframe(self._var_type)
866
- ):
867
- if self._var_type == Any:
868
- raise VarTypeError(
869
- "Could not index into var of type Any. (If you are trying to index into a state var, "
870
- "add the correct type annotation to the var.)"
871
- )
872
- raise VarTypeError(
873
- f"Var {self._var_name} of type {self._var_type} does not support indexing."
874
- )
875
-
876
- # The type of the indexed var.
877
- type_ = Any
878
-
879
- # Convert any vars to local vars.
880
- if isinstance(i, Var):
881
- i = i._replace(_var_is_local=True)
882
-
883
- # Handle list/tuple/str indexing.
884
- if types._issubclass(self._var_type, Union[List, Tuple, str]):
885
- # List/Tuple/String indices must be ints, slices, or vars.
886
- if (
887
- not isinstance(i, types.get_args(Union[int, slice, Var]))
888
- or isinstance(i, Var)
889
- and not i._var_type == int
890
- ):
891
- raise VarTypeError("Index must be an integer or an integer var.")
892
-
893
- # Handle slices first.
894
- if isinstance(i, slice):
895
- # Get the start and stop indices.
896
- start = i.start or 0
897
- stop = i.stop or "undefined"
898
-
899
- # Use the slice function.
900
- return self._replace(
901
- _var_name=f"{self._var_name}.slice({start}, {stop})",
902
- _var_is_string=False,
903
- )
904
-
905
- # Get the type of the indexed var.
906
- if types.is_generic_alias(self._var_type):
907
- index = i if not isinstance(i, Var) else 0
908
- type_ = types.get_args(self._var_type)
909
- type_ = type_[index % len(type_)] if type_ else Any
910
- elif types._issubclass(self._var_type, str):
911
- type_ = str
912
-
913
- # Use `at` to support negative indices.
914
- return self._replace(
915
- _var_name=f"{self._var_name}.at({i})",
916
- _var_type=type_,
917
- _var_is_string=False,
918
- )
919
-
920
- # Dictionary / dataframe indexing.
921
- # Tuples are currently not supported as indexes.
922
- if (
923
- (
924
- types._issubclass(self._var_type, Dict)
925
- or types.is_dataframe(self._var_type)
926
- )
927
- and not isinstance(i, types.get_args(Union[int, str, float, Var]))
928
- ) or (
929
- isinstance(i, Var)
930
- and not types._issubclass(
931
- i._var_type, types.get_args(Union[int, str, float])
932
- )
933
- ):
934
- raise VarTypeError(
935
- "Index must be one of the following types: int, str, int or str Var"
936
- )
937
- # Get the type of the indexed var.
938
- if isinstance(i, str):
939
- i = format.wrap(i, '"')
940
- type_ = (
941
- types.get_args(self._var_type)[1]
942
- if types.is_generic_alias(self._var_type)
943
- else Any
944
- )
945
-
946
- # Use normal indexing here.
947
- return self._replace(
948
- _var_name=f"{self._var_name}[{i}]",
949
- _var_type=type_,
950
- _var_is_string=False,
951
- )
952
-
953
- def __getattribute__(self, name: str) -> Any:
954
- """Get a var attribute.
955
-
956
- Args:
957
- name: The name of the attribute.
958
-
959
- Returns:
960
- The var attribute.
961
-
962
- Raises:
963
- VarAttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used.
964
- """
965
- try:
966
- var_attribute = super().__getattribute__(name)
967
- if (
968
- not name.startswith("_")
969
- and name not in Var.__dict__
970
- and name not in BaseVar.__dict__
971
- ):
972
- # Check if the attribute should be accessed through the Var instead of
973
- # accessing one of the Var operations
974
- type_ = types.get_attribute_access_type(
975
- super().__getattribute__("_var_type"), name
976
- )
977
- if type_ is not None:
978
- raise VarAttributeError(
979
- f"{name} is being accessed through the Var."
980
- )
981
- # Return the attribute as-is.
982
- return var_attribute
983
- except VarAttributeError:
984
- raise # fall back to __getattr__ anyway
985
-
986
- def __getattr__(self, name: str) -> Var:
987
- """Get a var attribute.
988
-
989
- Args:
990
- name: The name of the attribute.
991
-
992
- Returns:
993
- The var attribute.
994
-
995
- Raises:
996
- VarAttributeError: If the var is wrongly annotated or can't find attribute.
997
- VarTypeError: If an annotation to the var isn't provided.
998
- """
999
- # Check if the attribute is one of the class fields.
1000
- if not name.startswith("_"):
1001
- if self._var_type == Any:
1002
- raise VarTypeError(
1003
- f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`"
1004
- ) from None
1005
- is_optional = types.is_optional(self._var_type)
1006
- type_ = types.get_attribute_access_type(self._var_type, name)
1007
-
1008
- if type_ is not None:
1009
- return self._replace(
1010
- _var_name=f"{self._var_name}{'?' if is_optional else ''}.{name}",
1011
- _var_type=type_,
1012
- _var_is_string=False,
1013
- )
1014
-
1015
- if name in REPLACED_NAMES:
1016
- raise VarAttributeError(
1017
- f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
1018
- )
1019
-
1020
- raise VarAttributeError(
1021
- f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated "
1022
- f"wrongly."
1023
- )
1024
-
1025
- raise VarAttributeError(
1026
- f"The State var has no attribute '{name}' or may have been annotated wrongly.",
1027
- )
1028
-
1029
- def operation(
1030
- self,
1031
- op: str = "",
1032
- other: Var | None = None,
1033
- type_: Type | None = None,
1034
- flip: bool = False,
1035
- fn: str | None = None,
1036
- invoke_fn: bool = False,
1037
- ) -> Var:
1038
- """Perform an operation on a var.
1039
-
1040
- Args:
1041
- op: The operation to perform.
1042
- other: The other var to perform the operation on.
1043
- type_: The type of the operation result.
1044
- flip: Whether to flip the order of the operation.
1045
- fn: A function to apply to the operation.
1046
- invoke_fn: Whether to invoke the function.
1047
-
1048
- Returns:
1049
- The operation result.
1050
-
1051
- Raises:
1052
- VarTypeError: If the operation between two operands is invalid.
1053
- VarValueError: If flip is set to true and value of operand is not provided
1054
- """
1055
- from reflex.utils import format
1056
-
1057
- if isinstance(other, str):
1058
- other = Var.create(json.dumps(other), _var_is_string=False)
1059
- else:
1060
- other = Var.create(other, _var_is_string=False)
1061
-
1062
- type_ = type_ or self._var_type
1063
-
1064
- if other is None and flip:
1065
- raise VarValueError(
1066
- "flip_operands cannot be set to True if the value of 'other' operand is not provided"
1067
- )
1068
-
1069
- left_operand, right_operand = (other, self) if flip else (self, other)
1070
-
1071
- def get_operand_full_name(operand):
1072
- # operand vars that are string literals need to be wrapped in back ticks.
1073
- return (
1074
- operand._var_name_unwrapped
1075
- if operand._var_is_string
1076
- and not operand._var_state
1077
- and operand._var_is_local
1078
- else operand._var_full_name
1079
- )
1080
-
1081
- if other is not None:
1082
- # check if the operation between operands is valid.
1083
- if op and not self.is_valid_operation(
1084
- types.get_base_class(left_operand._var_type), # type: ignore
1085
- types.get_base_class(right_operand._var_type), # type: ignore
1086
- op,
1087
- ):
1088
- raise VarTypeError(
1089
- f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}" # type: ignore
1090
- )
1091
-
1092
- left_operand_full_name = get_operand_full_name(left_operand)
1093
- right_operand_full_name = get_operand_full_name(right_operand)
1094
-
1095
- left_operand_full_name = format.wrap(left_operand_full_name, "(")
1096
- right_operand_full_name = format.wrap(right_operand_full_name, "(")
1097
-
1098
- # apply function to operands
1099
- if fn is not None:
1100
- if invoke_fn:
1101
- # invoke the function on left operand.
1102
- operation_name = (
1103
- f"{left_operand_full_name}.{fn}({right_operand_full_name})" # type: ignore
1104
- )
1105
- else:
1106
- # pass the operands as arguments to the function.
1107
- operation_name = (
1108
- f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
1109
- )
1110
- operation_name = f"{fn}({operation_name})"
1111
- else:
1112
- # apply operator to operands (left operand <operator> right_operand)
1113
- operation_name = (
1114
- f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
1115
- )
1116
- operation_name = format.wrap(operation_name, "(")
1117
- else:
1118
- # apply operator to left operand (<operator> left_operand)
1119
- operation_name = f"{op}{get_operand_full_name(self)}"
1120
- # apply function to operands
1121
- if fn is not None:
1122
- operation_name = (
1123
- f"{fn}({operation_name})"
1124
- if not invoke_fn
1125
- else f"{get_operand_full_name(self)}.{fn}()"
1126
- )
1127
-
1128
- return self._replace(
1129
- _var_name=operation_name,
1130
- _var_type=type_,
1131
- _var_is_string=False,
1132
- _var_full_name_needs_state_prefix=False,
1133
- merge_var_data=other._var_data if other is not None else None,
1134
- )
1135
-
1136
- @staticmethod
1137
- def is_valid_operation(
1138
- operand1_type: Type, operand2_type: Type, operator: str
1139
- ) -> bool:
1140
- """Check if an operation between two operands is valid.
1141
-
1142
- Args:
1143
- operand1_type: Type of the operand
1144
- operand2_type: Type of the second operand
1145
- operator: The operator.
1146
-
1147
- Returns:
1148
- Whether operation is valid or not
1149
-
1150
- """
1151
- if operator in ALL_OPS or operator in DELIMITERS:
1152
- return True
1153
-
1154
- # bools are subclasses of ints
1155
- pair = tuple(
1156
- sorted(
1157
- [
1158
- int if operand1_type == bool else operand1_type,
1159
- int if operand2_type == bool else operand2_type,
1160
- ],
1161
- key=lambda x: x.__name__,
1162
- )
1163
- )
1164
- return pair in OPERATION_MAPPING and operator in OPERATION_MAPPING[pair]
1165
-
1166
- def compare(self, op: str, other: Var) -> Var:
1167
- """Compare two vars with inequalities.
1168
-
1169
- Args:
1170
- op: The comparison operator.
1171
- other: The other var to compare with.
1172
-
1173
- Returns:
1174
- The comparison result.
1175
- """
1176
- return self.operation(op, other, bool)
1177
-
1178
- def __invert__(self) -> Var:
1179
- """Invert a var.
1180
-
1181
- Returns:
1182
- The inverted var.
1183
- """
1184
- return self.operation("!", type_=bool)
1185
-
1186
- def __neg__(self) -> Var:
1187
- """Negate a var.
1188
-
1189
- Returns:
1190
- The negated var.
1191
- """
1192
- return self.operation(fn="-")
1193
-
1194
- def __abs__(self) -> Var:
1195
- """Get the absolute value of a var.
1196
-
1197
- Returns:
1198
- A var with the absolute value.
1199
- """
1200
- return self.operation(fn="Math.abs")
1201
-
1202
- def length(self) -> Var:
1203
- """Get the length of a list var.
1204
-
1205
- Returns:
1206
- A var with the absolute value.
1207
-
1208
- Raises:
1209
- VarTypeError: If the var is not a list.
1210
- """
1211
- if not types._issubclass(self._var_type, List):
1212
- raise VarTypeError(f"Cannot get length of non-list var {self}.")
1213
- return self._replace(
1214
- _var_name=f"{self._var_name}.length",
1215
- _var_type=int,
1216
- _var_is_string=False,
1217
- )
1218
-
1219
- def _type(self) -> Var:
1220
- """Get the type of the Var in Javascript.
1221
-
1222
- Returns:
1223
- A var representing the type check.
1224
- """
1225
- return self._replace(
1226
- _var_name=f"typeof {self._var_full_name}",
1227
- _var_type=str,
1228
- _var_is_string=False,
1229
- _var_full_name_needs_state_prefix=False,
1230
- )
1231
-
1232
- def __eq__(self, other: Union[Var, Type]) -> Var:
1233
- """Perform an equality comparison.
1234
-
1235
- Args:
1236
- other: The other var to compare with.
1237
-
1238
- Returns:
1239
- A var representing the equality comparison.
1240
- """
1241
- for python_types, js_type in PYTHON_JS_TYPE_MAP.items():
1242
- if not isinstance(other, Var) and other in python_types:
1243
- return self.compare("===", Var.create(js_type, _var_is_string=True)) # type: ignore
1244
- return self.compare("===", other)
1245
-
1246
- def __ne__(self, other: Union[Var, Type]) -> Var:
1247
- """Perform an inequality comparison.
1248
-
1249
- Args:
1250
- other: The other var to compare with.
1251
-
1252
- Returns:
1253
- A var representing the inequality comparison.
1254
- """
1255
- for python_types, js_type in PYTHON_JS_TYPE_MAP.items():
1256
- if not isinstance(other, Var) and other in python_types:
1257
- return self.compare("!==", Var.create(js_type, _var_is_string=True)) # type: ignore
1258
- return self.compare("!==", other)
1259
-
1260
- def __gt__(self, other: Var) -> Var:
1261
- """Perform a greater than comparison.
1262
-
1263
- Args:
1264
- other: The other var to compare with.
1265
-
1266
- Returns:
1267
- A var representing the greater than comparison.
1268
- """
1269
- return self.compare(">", other)
1270
-
1271
- def __ge__(self, other: Var) -> Var:
1272
- """Perform a greater than or equal to comparison.
1273
-
1274
- Args:
1275
- other: The other var to compare with.
1276
-
1277
- Returns:
1278
- A var representing the greater than or equal to comparison.
1279
- """
1280
- return self.compare(">=", other)
1281
-
1282
- def __lt__(self, other: Var) -> Var:
1283
- """Perform a less than comparison.
1284
-
1285
- Args:
1286
- other: The other var to compare with.
1287
-
1288
- Returns:
1289
- A var representing the less than comparison.
1290
- """
1291
- return self.compare("<", other)
1292
-
1293
- def __le__(self, other: Var) -> Var:
1294
- """Perform a less than or equal to comparison.
1295
-
1296
- Args:
1297
- other: The other var to compare with.
1298
-
1299
- Returns:
1300
- A var representing the less than or equal to comparison.
1301
- """
1302
- return self.compare("<=", other)
1303
-
1304
- def __add__(self, other: Var, flip=False) -> Var:
1305
- """Add two vars.
1306
-
1307
- Args:
1308
- other: The other var to add.
1309
- flip: Whether to flip operands.
1310
-
1311
- Returns:
1312
- A var representing the sum.
1313
- """
1314
- other_type = other._var_type if isinstance(other, Var) else type(other)
1315
- # For list-list addition, javascript concatenates the content of the lists instead of
1316
- # merging the list, and for that reason we use the spread operator available through spreadArraysOrObjects
1317
- # utility function
1318
- if (
1319
- types.get_base_class(self._var_type) == list
1320
- and types.get_base_class(other_type) == list
1321
- ):
1322
- return self.operation(
1323
- ",", other, fn="spreadArraysOrObjects", flip=flip
1324
- )._replace(
1325
- merge_var_data=VarData(
1326
- imports={
1327
- f"/{constants.Dirs.STATE_PATH}": [
1328
- ImportVar(tag="spreadArraysOrObjects")
1329
- ]
1330
- },
1331
- ),
1332
- )
1333
- return self.operation("+", other, flip=flip)
1334
-
1335
- def __radd__(self, other: Var) -> Var:
1336
- """Add two vars.
1337
-
1338
- Args:
1339
- other: The other var to add.
1340
-
1341
- Returns:
1342
- A var representing the sum.
1343
- """
1344
- return self.__add__(other=other, flip=True)
1345
-
1346
- def __sub__(self, other: Var) -> Var:
1347
- """Subtract two vars.
1348
-
1349
- Args:
1350
- other: The other var to subtract.
1351
-
1352
- Returns:
1353
- A var representing the difference.
1354
- """
1355
- return self.operation("-", other)
1356
-
1357
- def __rsub__(self, other: Var) -> Var:
1358
- """Subtract two vars.
1359
-
1360
- Args:
1361
- other: The other var to subtract.
1362
-
1363
- Returns:
1364
- A var representing the difference.
1365
- """
1366
- return self.operation("-", other, flip=True)
1367
-
1368
- def __mul__(self, other: Var, flip=True) -> Var:
1369
- """Multiply two vars.
1370
-
1371
- Args:
1372
- other: The other var to multiply.
1373
- flip: Whether to flip operands
1374
-
1375
- Returns:
1376
- A var representing the product.
1377
- """
1378
- other_type = other._var_type if isinstance(other, Var) else type(other)
1379
- # For str-int multiplication, we use the repeat function.
1380
- # i.e "hello" * 2 is equivalent to "hello".repeat(2) in js.
1381
- if (types.get_base_class(self._var_type), types.get_base_class(other_type)) in [
1382
- (int, str),
1383
- (str, int),
1384
- ]:
1385
- return self.operation(other=other, fn="repeat", invoke_fn=True)
1386
-
1387
- # For list-int multiplication, we use the Array function.
1388
- # i.e ["hello"] * 2 is equivalent to Array(2).fill().map(() => ["hello"]).flat() in js.
1389
- if (types.get_base_class(self._var_type), types.get_base_class(other_type)) in [
1390
- (int, list),
1391
- (list, int),
1392
- ]:
1393
- other_name = other._var_full_name if isinstance(other, Var) else other
1394
- name = f"Array({other_name}).fill().map(() => {self._var_full_name}).flat()"
1395
- return self._replace(
1396
- _var_name=name,
1397
- _var_type=str,
1398
- _var_is_string=False,
1399
- _var_full_name_needs_state_prefix=False,
1400
- )
1401
-
1402
- return self.operation("*", other)
1403
-
1404
- def __rmul__(self, other: Var) -> Var:
1405
- """Multiply two vars.
1406
-
1407
- Args:
1408
- other: The other var to multiply.
1409
-
1410
- Returns:
1411
- A var representing the product.
1412
- """
1413
- return self.__mul__(other=other, flip=True)
1414
-
1415
- def __pow__(self, other: Var) -> Var:
1416
- """Raise a var to a power.
1417
-
1418
- Args:
1419
- other: The power to raise to.
1420
-
1421
- Returns:
1422
- A var representing the power.
1423
- """
1424
- return self.operation(",", other, fn="Math.pow")
1425
-
1426
- def __rpow__(self, other: Var) -> Var:
1427
- """Raise a var to a power.
1428
-
1429
- Args:
1430
- other: The power to raise to.
1431
-
1432
- Returns:
1433
- A var representing the power.
1434
- """
1435
- return self.operation(",", other, flip=True, fn="Math.pow")
1436
-
1437
- def __truediv__(self, other: Var) -> Var:
1438
- """Divide two vars.
1439
-
1440
- Args:
1441
- other: The other var to divide.
1442
-
1443
- Returns:
1444
- A var representing the quotient.
1445
- """
1446
- return self.operation("/", other)
1447
-
1448
- def __rtruediv__(self, other: Var) -> Var:
1449
- """Divide two vars.
1450
-
1451
- Args:
1452
- other: The other var to divide.
1453
-
1454
- Returns:
1455
- A var representing the quotient.
1456
- """
1457
- return self.operation("/", other, flip=True)
1458
-
1459
- def __floordiv__(self, other: Var) -> Var:
1460
- """Divide two vars.
1461
-
1462
- Args:
1463
- other: The other var to divide.
1464
-
1465
- Returns:
1466
- A var representing the quotient.
1467
- """
1468
- return self.operation("/", other, fn="Math.floor")
1469
-
1470
- def __mod__(self, other: Var) -> Var:
1471
- """Get the remainder of two vars.
1472
-
1473
- Args:
1474
- other: The other var to divide.
1475
-
1476
- Returns:
1477
- A var representing the remainder.
1478
- """
1479
- return self.operation("%", other)
1480
-
1481
- def __rmod__(self, other: Var) -> Var:
1482
- """Get the remainder of two vars.
1483
-
1484
- Args:
1485
- other: The other var to divide.
1486
-
1487
- Returns:
1488
- A var representing the remainder.
1489
- """
1490
- return self.operation("%", other, flip=True)
1491
-
1492
- def __and__(self, other: Var) -> Var:
1493
- """Perform a logical and.
1494
-
1495
- Args:
1496
- other: The other var to perform the logical AND with.
1497
-
1498
- Returns:
1499
- A var representing the logical AND.
1500
-
1501
- Note:
1502
- This method provides behavior specific to JavaScript, where it returns the JavaScript
1503
- equivalent code (using the '&&' operator) of a logical AND operation.
1504
- In JavaScript, the
1505
- logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
1506
- by returning the equivalent code as a Var instance.
1507
-
1508
- In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
1509
- it challenging to override the behavior entirely.
1510
- Therefore, this method leverages the
1511
- bitwise AND '__and__' operator for custom JavaScript-like behavior.
1512
-
1513
- Example:
1514
- >>> var1 = Var.create(True)
1515
- >>> var2 = Var.create(False)
1516
- >>> js_code = var1 & var2
1517
- >>> print(js_code._var_full_name)
1518
- '(true && false)'
1519
- """
1520
- return self.operation("&&", other, type_=bool)
1521
-
1522
- def __rand__(self, other: Var) -> Var:
1523
- """Perform a logical and.
1524
-
1525
- Args:
1526
- other: The other var to perform the logical AND with.
1527
-
1528
- Returns:
1529
- A var representing the logical AND.
1530
-
1531
- Note:
1532
- This method provides behavior specific to JavaScript, where it returns the JavaScript
1533
- equivalent code (using the '&&' operator) of a logical AND operation.
1534
- In JavaScript, the
1535
- logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
1536
- by returning the equivalent code as a Var instance.
1537
-
1538
- In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
1539
- it challenging to override the behavior entirely.
1540
- Therefore, this method leverages the
1541
- bitwise AND '__rand__' operator for custom JavaScript-like behavior.
1542
-
1543
- Example:
1544
- >>> var1 = Var.create(True)
1545
- >>> var2 = Var.create(False)
1546
- >>> js_code = var1 & var2
1547
- >>> print(js_code._var_full_name)
1548
- '(false && true)'
1549
- """
1550
- return self.operation("&&", other, type_=bool, flip=True)
1551
-
1552
- def __or__(self, other: Var) -> Var:
1553
- """Perform a logical or.
1554
-
1555
- Args:
1556
- other: The other var to perform the logical or with.
1557
-
1558
- Returns:
1559
- A var representing the logical or.
1560
-
1561
- Note:
1562
- This method provides behavior specific to JavaScript, where it returns the JavaScript
1563
- equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
1564
- logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
1565
- by returning the equivalent code as a Var instance.
1566
-
1567
- In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
1568
- it challenging to override the behavior entirely. Therefore, this method leverages the
1569
- bitwise OR '__or__' operator for custom JavaScript-like behavior.
1570
-
1571
- Example:
1572
- >>> var1 = Var.create(True)
1573
- >>> var2 = Var.create(False)
1574
- >>> js_code = var1 | var2
1575
- >>> print(js_code._var_full_name)
1576
- '(true || false)'
1577
- """
1578
- return self.operation("||", other, type_=bool)
1579
-
1580
- def __ror__(self, other: Var) -> Var:
1581
- """Perform a logical or.
1582
-
1583
- Args:
1584
- other: The other var to perform the logical or with.
1585
-
1586
- Returns:
1587
- A var representing the logical or.
1588
-
1589
- Note:
1590
- This method provides behavior specific to JavaScript, where it returns the JavaScript
1591
- equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
1592
- logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
1593
- by returning the equivalent code as a Var instance.
1594
-
1595
- In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
1596
- it challenging to override the behavior entirely. Therefore, this method leverages the
1597
- bitwise OR '__or__' operator for custom JavaScript-like behavior.
1598
-
1599
- Example:
1600
- >>> var1 = Var.create(True)
1601
- >>> var2 = Var.create(False)
1602
- >>> js_code = var1 | var2
1603
- >>> print(js_code)
1604
- 'false || true'
1605
- """
1606
- return self.operation("||", other, type_=bool, flip=True)
1607
-
1608
- def __contains__(self, _: Any) -> Var:
1609
- """Override the 'in' operator to alert the user that it is not supported.
1610
-
1611
- Raises:
1612
- VarTypeError: the operation is not supported
1613
- """
1614
- raise VarTypeError(
1615
- "'in' operator not supported for Var types, use Var.contains() instead."
1616
- )
1617
-
1618
- def contains(self, other: Any, field: Union[Var, None] = None) -> Var:
1619
- """Check if a var contains the object `other`.
1620
-
1621
- Args:
1622
- other: The object to check.
1623
- field: Optionally specify a field to check on both object and the other var.
1624
-
1625
- Raises:
1626
- VarTypeError: If the var is not a valid type: dict, list, tuple or str.
1627
-
1628
- Returns:
1629
- A var representing the contain check.
1630
- """
1631
- if not (types._issubclass(self._var_type, Union[dict, list, tuple, str, set])):
1632
- raise VarTypeError(
1633
- f"Var {self._var_full_name} of type {self._var_type} does not support contains check."
1634
- )
1635
- method = (
1636
- "hasOwnProperty"
1637
- if types.get_base_class(self._var_type) == dict
1638
- else "includes"
1639
- )
1640
- if isinstance(other, str):
1641
- other = Var.create(json.dumps(other), _var_is_string=True)
1642
- elif not isinstance(other, Var):
1643
- other = Var.create(other, _var_is_string=False)
1644
- if types._issubclass(self._var_type, Dict):
1645
- return self._replace(
1646
- _var_name=f"{self._var_name}.{method}({other._var_full_name})",
1647
- _var_type=bool,
1648
- _var_is_string=False,
1649
- merge_var_data=other._var_data,
1650
- )
1651
- else: # str, list, tuple
1652
- # For strings, the left operand must be a string.
1653
- if types._issubclass(self._var_type, str) and not types._issubclass(
1654
- other._var_type, str
1655
- ):
1656
- raise VarTypeError(
1657
- f"'in <string>' requires string as left operand, not {other._var_type}"
1658
- )
1659
-
1660
- _var_name = None
1661
- if field is None:
1662
- _var_name = f"{self._var_name}.includes({other._var_full_name})"
1663
- else:
1664
- field = Var.create_safe(field, _var_is_string=isinstance(field, str))
1665
- _var_name = f"{self._var_name}.some(e=>e[{field._var_name_unwrapped}]==={other._var_full_name})"
1666
-
1667
- return self._replace(
1668
- _var_name=_var_name,
1669
- _var_type=bool,
1670
- _var_is_string=False,
1671
- merge_var_data=other._var_data,
1672
- )
1673
-
1674
- def reverse(self) -> Var:
1675
- """Reverse a list var.
1676
-
1677
- Raises:
1678
- VarTypeError: If the var is not a list.
1679
-
1680
- Returns:
1681
- A var with the reversed list.
1682
- """
1683
- if not types._issubclass(self._var_type, list):
1684
- raise VarTypeError(f"Cannot reverse non-list var {self._var_full_name}.")
1685
-
1686
- return self._replace(
1687
- _var_name=f"[...{self._var_full_name}].reverse()",
1688
- _var_is_string=False,
1689
- _var_full_name_needs_state_prefix=False,
1690
- )
1691
-
1692
- def lower(self) -> Var:
1693
- """Convert a string var to lowercase.
1694
-
1695
- Returns:
1696
- A var with the lowercase string.
1697
-
1698
- Raises:
1699
- VarTypeError: If the var is not a string.
1700
- """
1701
- if not types._issubclass(self._var_type, str):
1702
- raise VarTypeError(
1703
- f"Cannot convert non-string var {self._var_full_name} to lowercase."
1704
- )
1705
-
1706
- return self._replace(
1707
- _var_name=f"{self._var_name}.toLowerCase()",
1708
- _var_is_string=False,
1709
- _var_type=str,
1710
- )
1711
-
1712
- def upper(self) -> Var:
1713
- """Convert a string var to uppercase.
1714
-
1715
- Returns:
1716
- A var with the uppercase string.
1717
-
1718
- Raises:
1719
- VarTypeError: If the var is not a string.
1720
- """
1721
- if not types._issubclass(self._var_type, str):
1722
- raise VarTypeError(
1723
- f"Cannot convert non-string var {self._var_full_name} to uppercase."
1724
- )
1725
-
1726
- return self._replace(
1727
- _var_name=f"{self._var_name}.toUpperCase()",
1728
- _var_is_string=False,
1729
- _var_type=str,
1730
- )
1731
-
1732
- def strip(self, other: str | Var[str] = " ") -> Var:
1733
- """Strip a string var.
1734
-
1735
- Args:
1736
- other: The string to strip the var with.
1737
-
1738
- Returns:
1739
- A var with the stripped string.
1740
-
1741
- Raises:
1742
- VarTypeError: If the var is not a string.
1743
- """
1744
- if not types._issubclass(self._var_type, str):
1745
- raise VarTypeError(f"Cannot strip non-string var {self._var_full_name}.")
1746
-
1747
- other = (
1748
- Var.create_safe(json.dumps(other), _var_is_string=False)
1749
- if isinstance(other, str)
1750
- else other
1751
- )
1752
-
1753
- return self._replace(
1754
- _var_name=f"{self._var_name}.replace(/^${other._var_full_name}|${other._var_full_name}$/g, '')",
1755
- _var_is_string=False,
1756
- merge_var_data=other._var_data,
1757
- )
1758
-
1759
- def split(self, other: str | Var[str] = " ") -> Var:
1760
- """Split a string var into a list.
1761
-
1762
- Args:
1763
- other: The string to split the var with.
1764
-
1765
- Returns:
1766
- A var with the list.
1767
-
1768
- Raises:
1769
- VarTypeError: If the var is not a string.
1770
- """
1771
- if not types._issubclass(self._var_type, str):
1772
- raise VarTypeError(f"Cannot split non-string var {self._var_full_name}.")
1773
-
1774
- other = (
1775
- Var.create_safe(json.dumps(other), _var_is_string=False)
1776
- if isinstance(other, str)
1777
- else other
1778
- )
1779
-
1780
- return self._replace(
1781
- _var_name=f"{self._var_name}.split({other._var_full_name})",
1782
- _var_is_string=False,
1783
- _var_type=List[str],
1784
- merge_var_data=other._var_data,
1785
- )
1786
-
1787
- def join(self, other: str | Var[str] | None = None) -> Var:
1788
- """Join a list var into a string.
1789
-
1790
- Args:
1791
- other: The string to join the list with.
1792
-
1793
- Returns:
1794
- A var with the string.
1795
-
1796
- Raises:
1797
- VarTypeError: If the var is not a list.
1798
- """
1799
- if not types._issubclass(self._var_type, list):
1800
- raise VarTypeError(f"Cannot join non-list var {self._var_full_name}.")
1801
-
1802
- if other is None:
1803
- other = Var.create_safe('""', _var_is_string=False)
1804
- if isinstance(other, str):
1805
- other = Var.create_safe(json.dumps(other), _var_is_string=False)
1806
- else:
1807
- other = Var.create_safe(other, _var_is_string=False)
1808
-
1809
- return self._replace(
1810
- _var_name=f"{self._var_name}.join({other._var_full_name})",
1811
- _var_is_string=False,
1812
- _var_type=str,
1813
- merge_var_data=other._var_data,
1814
- )
1815
-
1816
- def foreach(self, fn: Callable) -> Var:
1817
- """Return a list of components. after doing a foreach on this var.
1818
-
1819
- Args:
1820
- fn: The function to call on each component.
1821
-
1822
- Returns:
1823
- A var representing foreach operation.
1824
-
1825
- Raises:
1826
- VarTypeError: If the var is not a list.
1827
- """
1828
- inner_types = get_args(self._var_type)
1829
- if not inner_types:
1830
- raise VarTypeError(
1831
- f"Cannot foreach over non-sequence var {self._var_full_name} of type {self._var_type}."
1832
- )
1833
- arg = BaseVar(
1834
- _var_name=get_unique_variable_name(),
1835
- _var_type=inner_types[0],
1836
- )
1837
- index = BaseVar(
1838
- _var_name=get_unique_variable_name(),
1839
- _var_type=int,
1840
- )
1841
- fn_signature = inspect.signature(fn)
1842
- fn_args = (arg, index)
1843
- fn_ret = fn(*fn_args[: len(fn_signature.parameters)])
1844
- return self._replace(
1845
- _var_name=f"{self._var_full_name}.map(({arg._var_name}, {index._var_name}) => {fn_ret})",
1846
- _var_is_string=False,
1847
- )
1848
-
1849
- @classmethod
1850
- def range(
1851
- cls,
1852
- v1: Var | int = 0,
1853
- v2: Var | int | None = None,
1854
- step: Var | int | None = None,
1855
- ) -> Var:
1856
- """Return an iterator over indices from v1 to v2 (or 0 to v1).
1857
-
1858
- Args:
1859
- v1: The start of the range or end of range if v2 is not given.
1860
- v2: The end of the range.
1861
- step: The number of numbers between each item.
1862
-
1863
- Returns:
1864
- A var representing range operation.
1865
-
1866
- Raises:
1867
- VarTypeError: If the var is not an int.
1868
- """
1869
- if not isinstance(v1, Var):
1870
- v1 = Var.create_safe(v1)
1871
- if v1._var_type != int:
1872
- raise VarTypeError(f"Cannot get range on non-int var {v1._var_full_name}.")
1873
- if not isinstance(v2, Var):
1874
- v2 = Var.create(v2)
1875
- if v2 is None:
1876
- v2 = Var.create_safe("undefined", _var_is_string=False)
1877
- elif v2._var_type != int:
1878
- raise VarTypeError(f"Cannot get range on non-int var {v2._var_full_name}.")
1879
-
1880
- if not isinstance(step, Var):
1881
- step = Var.create(step)
1882
- if step is None:
1883
- step = Var.create_safe(1)
1884
- elif step._var_type != int:
1885
- raise VarTypeError(
1886
- f"Cannot get range on non-int var {step._var_full_name}."
1887
- )
1888
-
1889
- return BaseVar(
1890
- _var_name=f"Array.from(range({v1._var_full_name}, {v2._var_full_name}, {step._var_name}))",
1891
- _var_type=List[int],
1892
- _var_is_local=False,
1893
- _var_data=VarData.merge(
1894
- v1._var_data,
1895
- v2._var_data,
1896
- step._var_data,
1897
- VarData(
1898
- imports={
1899
- "/utils/helpers/range.js": [
1900
- ImportVar(tag="range", is_default=True),
1901
- ],
1902
- },
1903
- ),
1904
- ),
1905
- )
1906
-
1907
- def to(self, type_: Type) -> Var:
1908
- """Convert the type of the var.
1909
-
1910
- Args:
1911
- type_: The type to convert to.
1912
-
1913
- Returns:
1914
- The converted var.
1915
- """
1916
- return self._replace(_var_type=type_)
1917
-
1918
- def as_ref(self) -> Var:
1919
- """Convert the var to a ref.
1920
-
1921
- Returns:
1922
- The var as a ref.
1923
- """
1924
- return self._replace(
1925
- _var_name=f"refs['{self._var_full_name}']",
1926
- _var_is_local=True,
1927
- _var_is_string=False,
1928
- _var_full_name_needs_state_prefix=False,
1929
- merge_var_data=VarData(
1930
- imports={
1931
- f"/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")],
1932
- },
1933
- ),
1934
- )
1935
-
1936
- @property
1937
- def _var_full_name(self) -> str:
1938
- """Get the full name of the var.
1939
-
1940
- Returns:
1941
- The full name of the var.
1942
- """
1943
- from reflex.utils import format
1944
-
1945
- if not self._var_full_name_needs_state_prefix:
1946
- return self._var_name
1947
- return (
1948
- self._var_name
1949
- if self._var_data is None or self._var_data.state == ""
1950
- else ".".join(
1951
- [format.format_state_name(self._var_data.state), self._var_name]
1952
- )
1953
- )
1954
-
1955
- def _var_set_state(self, state: Type[BaseState] | str) -> Any:
1956
- """Set the state of the var.
1957
-
1958
- Args:
1959
- state: The state to set or the full name of the state.
1960
-
1961
- Returns:
1962
- The var with the set state.
1963
- """
1964
- from reflex.utils import format
1965
-
1966
- state_name = state if isinstance(state, str) else state.get_full_name()
1967
- new_var_data = VarData(
1968
- state=state_name,
1969
- hooks={
1970
- "const {0} = useContext(StateContexts.{0})".format(
1971
- format.format_state_name(state_name)
1972
- ): None
1973
- },
1974
- imports={
1975
- f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
1976
- "react": [ImportVar(tag="useContext")],
1977
- },
1978
- )
1979
- self._var_data = VarData.merge(self._var_data, new_var_data)
1980
- self._var_full_name_needs_state_prefix = True
1981
- return self
1982
-
1983
- @property
1984
- def _var_state(self) -> str:
1985
- """Compat method for getting the state.
1986
-
1987
- Returns:
1988
- The state name associated with the var.
1989
- """
1990
- return self._var_data.state if self._var_data else ""
1991
-
1992
- def _get_all_var_data(self) -> VarData | None:
1993
- """Get all the var data.
1994
-
1995
- Returns:
1996
- The var data.
1997
- """
1998
- return self._var_data
1999
-
2000
- def json(self) -> str:
2001
- """Serialize the var to a JSON string.
2002
-
2003
- Raises:
2004
- NotImplementedError: If the method is not implemented.
471
+ NotImplementedError: If the method is not implemented.
2005
472
  """
2006
473
  raise NotImplementedError("Var subclasses must implement the json method.")
2007
474
 
2008
- @property
2009
- def _var_name_unwrapped(self) -> str:
2010
- """Get the var str without wrapping in curly braces.
2011
-
2012
- Returns:
2013
- The str var without the wrapped curly braces
2014
- """
2015
- from reflex.style import Style
2016
-
2017
- generic_alias = types.is_generic_alias(self._var_type)
2018
-
2019
- type_ = get_origin(self._var_type) if generic_alias else self._var_type
2020
- wrapped_var = str(self)
2021
-
2022
- return (
2023
- wrapped_var
2024
- if not self._var_state
2025
- and not generic_alias
2026
- and (types._issubclass(type_, dict) or types._issubclass(type_, Style))
2027
- else wrapped_var.strip("{}")
2028
- )
2029
-
2030
-
2031
- # Allow automatic serialization of Var within JSON structures
2032
- serializers.serializer(_encode_var)
2033
-
2034
-
2035
- @dataclasses.dataclass(
2036
- eq=False,
2037
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2038
- )
2039
- class BaseVar(Var):
2040
- """A base (non-computed) var of the app state."""
2041
-
2042
- # The name of the var.
2043
- _var_name: str = dataclasses.field()
2044
-
2045
- # The type of the var.
2046
- _var_type: Type = dataclasses.field(default=Any)
2047
-
2048
- # Whether this is a local javascript variable.
2049
- _var_is_local: bool = dataclasses.field(default=False)
2050
-
2051
- # Whether the var is a string literal.
2052
- _var_is_string: bool = dataclasses.field(default=False)
2053
-
2054
- # _var_full_name should be prefixed with _var_state
2055
- _var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
2056
-
2057
- # Extra metadata associated with the Var
2058
- _var_data: Optional[VarData] = dataclasses.field(default=None)
2059
-
2060
- def __hash__(self) -> int:
2061
- """Define a hash function for a var.
2062
-
2063
- Returns:
2064
- The hash of the var.
2065
- """
2066
- return hash((self._var_name, str(self._var_type)))
2067
-
2068
- def get_default_value(self) -> Any:
2069
- """Get the default value of the var.
2070
-
2071
- Returns:
2072
- The default value of the var.
2073
-
2074
- Raises:
2075
- ImportError: If the var is a dataframe and pandas is not installed.
2076
- """
2077
- if types.is_optional(self._var_type):
2078
- return None
2079
-
2080
- type_ = (
2081
- get_origin(self._var_type)
2082
- if types.is_generic_alias(self._var_type)
2083
- else self._var_type
2084
- )
2085
- if type_ is Literal:
2086
- args = get_args(self._var_type)
2087
- return args[0] if args else None
2088
- if issubclass(type_, str):
2089
- return ""
2090
- if issubclass(type_, types.get_args(Union[int, float])):
2091
- return 0
2092
- if issubclass(type_, bool):
2093
- return False
2094
- if issubclass(type_, list):
2095
- return []
2096
- if issubclass(type_, dict):
2097
- return {}
2098
- if issubclass(type_, tuple):
2099
- return ()
2100
- if types.is_dataframe(type_):
2101
- try:
2102
- import pandas as pd
2103
-
2104
- return pd.DataFrame()
2105
- except ImportError as e:
2106
- raise ImportError(
2107
- "Please install pandas to use dataframes in your app."
2108
- ) from e
2109
- return set() if issubclass(type_, set) else None
2110
-
2111
- def get_setter_name(self, include_state: bool = True) -> str:
2112
- """Get the name of the var's generated setter function.
2113
-
2114
- Args:
2115
- include_state: Whether to include the state name in the setter name.
2116
-
2117
- Returns:
2118
- The name of the setter function.
2119
- """
2120
- setter = constants.SETTER_PREFIX + self._var_name
2121
- if self._var_data is None:
2122
- return setter
2123
- if not include_state or self._var_data.state == "":
2124
- return setter
2125
- return ".".join((self._var_data.state, setter))
2126
-
2127
- def get_setter(self) -> Callable[[BaseState, Any], None]:
2128
- """Get the var's setter function.
2129
-
2130
- Returns:
2131
- A function that that creates a setter for the var.
2132
- """
2133
-
2134
- def setter(state: BaseState, value: Any):
2135
- """Get the setter for the var.
2136
-
2137
- Args:
2138
- state: The state within which we add the setter function.
2139
- value: The value to set.
2140
- """
2141
- if self._var_type in [int, float]:
2142
- try:
2143
- value = self._var_type(value)
2144
- setattr(state, self._var_name, value)
2145
- except ValueError:
2146
- console.debug(
2147
- f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
2148
- )
2149
- else:
2150
- setattr(state, self._var_name, value)
2151
-
2152
- setter.__qualname__ = self.get_setter_name()
2153
-
2154
- return setter
2155
-
2156
-
2157
- @dataclasses.dataclass(init=False, eq=False)
2158
- class ComputedVar(Var, property):
2159
- """A field with computed getters."""
2160
-
2161
- # Whether to track dependencies and cache computed values
2162
- _cache: bool = dataclasses.field(default=False)
2163
-
2164
- # Whether the computed var is a backend var
2165
- _backend: bool = dataclasses.field(default=False)
2166
-
2167
- # The initial value of the computed var
2168
- _initial_value: Any | types.Unset = dataclasses.field(default=types.Unset())
2169
-
2170
- # Explicit var dependencies to track
2171
- _static_deps: set[str] = dataclasses.field(default_factory=set)
2172
-
2173
- # Whether var dependencies should be auto-determined
2174
- _auto_deps: bool = dataclasses.field(default=True)
2175
-
2176
- # Interval at which the computed var should be updated
2177
- _update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
2178
-
2179
- # The name of the var.
2180
- _var_name: str = dataclasses.field()
2181
-
2182
- # The type of the var.
2183
- _var_type: Type = dataclasses.field(default=Any)
2184
-
2185
- # Whether this is a local javascript variable.
2186
- _var_is_local: bool = dataclasses.field(default=False)
2187
-
2188
- # Whether the var is a string literal.
2189
- _var_is_string: bool = dataclasses.field(default=False)
2190
-
2191
- # _var_full_name should be prefixed with _var_state
2192
- _var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
2193
-
2194
- # Extra metadata associated with the Var
2195
- _var_data: Optional[VarData] = dataclasses.field(default=None)
2196
-
2197
- def __init__(
2198
- self,
2199
- fget: Callable[[BaseState], Any],
2200
- initial_value: Any | types.Unset = types.Unset(),
2201
- cache: bool = False,
2202
- deps: Optional[List[Union[str, Var]]] = None,
2203
- auto_deps: bool = True,
2204
- interval: Optional[Union[int, datetime.timedelta]] = None,
2205
- backend: bool | None = None,
2206
- **kwargs,
2207
- ):
2208
- """Initialize a ComputedVar.
2209
-
2210
- Args:
2211
- fget: The getter function.
2212
- initial_value: The initial value of the computed var.
2213
- cache: Whether to cache the computed value.
2214
- deps: Explicit var dependencies to track.
2215
- auto_deps: Whether var dependencies should be auto-determined.
2216
- interval: Interval at which the computed var should be updated.
2217
- backend: Whether the computed var is a backend var.
2218
- **kwargs: additional attributes to set on the instance
2219
-
2220
- Raises:
2221
- TypeError: If the computed var dependencies are not Var instances or var names.
2222
- """
2223
- if backend is None:
2224
- backend = fget.__name__.startswith("_")
2225
- self._backend = backend
2226
-
2227
- self._initial_value = initial_value
2228
- self._cache = cache
2229
- if isinstance(interval, int):
2230
- interval = datetime.timedelta(seconds=interval)
2231
- self._update_interval = interval
2232
- if deps is None:
2233
- deps = []
2234
- else:
2235
- for dep in deps:
2236
- if isinstance(dep, Var):
2237
- continue
2238
- if isinstance(dep, str) and dep != "":
2239
- continue
2240
- raise TypeError(
2241
- "ComputedVar dependencies must be Var instances or var names (non-empty strings)."
2242
- )
2243
- self._static_deps = {
2244
- dep._var_name if isinstance(dep, Var) else dep for dep in deps
2245
- }
2246
- self._auto_deps = auto_deps
2247
- property.__init__(self, fget)
2248
- kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
2249
- kwargs["_var_type"] = kwargs.pop("_var_type", self._determine_var_type())
2250
- BaseVar.__init__(self, **kwargs) # type: ignore
2251
-
2252
- @override
2253
- def _replace(self, merge_var_data=None, **kwargs: Any) -> ComputedVar:
2254
- """Replace the attributes of the ComputedVar.
2255
-
2256
- Args:
2257
- merge_var_data: VarData to merge into the existing VarData.
2258
- **kwargs: Var fields to update.
2259
-
2260
- Returns:
2261
- The new ComputedVar instance.
2262
-
2263
- Raises:
2264
- TypeError: If kwargs contains keys that are not allowed.
2265
- """
2266
- field_values = dict(
2267
- fget=kwargs.pop("fget", self.fget),
2268
- initial_value=kwargs.pop("initial_value", self._initial_value),
2269
- cache=kwargs.pop("cache", self._cache),
2270
- deps=kwargs.pop("deps", self._static_deps),
2271
- auto_deps=kwargs.pop("auto_deps", self._auto_deps),
2272
- interval=kwargs.pop("interval", self._update_interval),
2273
- backend=kwargs.pop("backend", self._backend),
2274
- _var_name=kwargs.pop("_var_name", self._var_name),
2275
- _var_type=kwargs.pop("_var_type", self._var_type),
2276
- _var_is_local=kwargs.pop("_var_is_local", self._var_is_local),
2277
- _var_is_string=kwargs.pop("_var_is_string", self._var_is_string),
2278
- _var_full_name_needs_state_prefix=kwargs.pop(
2279
- "_var_full_name_needs_state_prefix",
2280
- self._var_full_name_needs_state_prefix,
2281
- ),
2282
- _var_data=VarData.merge(self._var_data, merge_var_data),
2283
- )
2284
-
2285
- if kwargs:
2286
- unexpected_kwargs = ", ".join(kwargs.keys())
2287
- raise TypeError(f"Unexpected keyword arguments: {unexpected_kwargs}")
2288
-
2289
- return ComputedVar(**field_values)
2290
-
2291
- @property
2292
- def _cache_attr(self) -> str:
2293
- """Get the attribute used to cache the value on the instance.
2294
-
2295
- Returns:
2296
- An attribute name.
2297
- """
2298
- return f"__cached_{self._var_name}"
2299
-
2300
- @property
2301
- def _last_updated_attr(self) -> str:
2302
- """Get the attribute used to store the last updated timestamp.
2303
-
2304
- Returns:
2305
- An attribute name.
2306
- """
2307
- return f"__last_updated_{self._var_name}"
2308
-
2309
- def needs_update(self, instance: BaseState) -> bool:
2310
- """Check if the computed var needs to be updated.
2311
-
2312
- Args:
2313
- instance: The state instance that the computed var is attached to.
2314
-
2315
- Returns:
2316
- True if the computed var needs to be updated, False otherwise.
2317
- """
2318
- if self._update_interval is None:
2319
- return False
2320
- last_updated = getattr(instance, self._last_updated_attr, None)
2321
- if last_updated is None:
2322
- return True
2323
- return datetime.datetime.now() - last_updated > self._update_interval
2324
-
2325
- def __get__(self, instance: BaseState | None, owner):
2326
- """Get the ComputedVar value.
2327
-
2328
- If the value is already cached on the instance, return the cached value.
2329
-
2330
- Args:
2331
- instance: the instance of the class accessing this computed var.
2332
- owner: the class that this descriptor is attached to.
2333
-
2334
- Returns:
2335
- The value of the var for the given instance.
2336
- """
2337
- if instance is None or not self._cache:
2338
- return super().__get__(instance, owner)
2339
-
2340
- # handle caching
2341
- if not hasattr(instance, self._cache_attr) or self.needs_update(instance):
2342
- # Set cache attr on state instance.
2343
- setattr(instance, self._cache_attr, super().__get__(instance, owner))
2344
- # Ensure the computed var gets serialized to redis.
2345
- instance._was_touched = True
2346
- # Set the last updated timestamp on the state instance.
2347
- setattr(instance, self._last_updated_attr, datetime.datetime.now())
2348
- return getattr(instance, self._cache_attr)
2349
-
2350
- def _deps(
2351
- self,
2352
- objclass: Type,
2353
- obj: FunctionType | CodeType | None = None,
2354
- self_name: Optional[str] = None,
2355
- ) -> set[str]:
2356
- """Determine var dependencies of this ComputedVar.
2357
-
2358
- Save references to attributes accessed on "self". Recursively called
2359
- when the function makes a method call on "self" or define comprehensions
2360
- or nested functions that may reference "self".
2361
-
2362
- Args:
2363
- objclass: the class obj this ComputedVar is attached to.
2364
- obj: the object to disassemble (defaults to the fget function).
2365
- self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
2366
-
2367
- Returns:
2368
- A set of variable names accessed by the given obj.
2369
-
2370
- Raises:
2371
- VarValueError: if the function references the get_state, parent_state, or substates attributes
2372
- (cannot track deps in a related state, only implicitly via parent state).
2373
- """
2374
- if not self._auto_deps:
2375
- return self._static_deps
2376
- d = self._static_deps.copy()
2377
- if obj is None:
2378
- fget = property.__getattribute__(self, "fget")
2379
- if fget is not None:
2380
- obj = cast(FunctionType, fget)
2381
- else:
2382
- return set()
2383
- with contextlib.suppress(AttributeError):
2384
- # unbox functools.partial
2385
- obj = cast(FunctionType, obj.func) # type: ignore
2386
- with contextlib.suppress(AttributeError):
2387
- # unbox EventHandler
2388
- obj = cast(FunctionType, obj.fn) # type: ignore
2389
-
2390
- if self_name is None and isinstance(obj, FunctionType):
2391
- try:
2392
- # the first argument to the function is the name of "self" arg
2393
- self_name = obj.__code__.co_varnames[0]
2394
- except (AttributeError, IndexError):
2395
- self_name = None
2396
- if self_name is None:
2397
- # cannot reference attributes on self if method takes no args
2398
- return set()
2399
-
2400
- invalid_names = ["get_state", "parent_state", "substates", "get_substate"]
2401
- self_is_top_of_stack = False
2402
- for instruction in dis.get_instructions(obj):
2403
- if (
2404
- instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
2405
- and instruction.argval == self_name
2406
- ):
2407
- # bytecode loaded the class instance to the top of stack, next load instruction
2408
- # is referencing an attribute on self
2409
- self_is_top_of_stack = True
2410
- continue
2411
- if self_is_top_of_stack and instruction.opname in (
2412
- "LOAD_ATTR",
2413
- "LOAD_METHOD",
2414
- ):
2415
- try:
2416
- ref_obj = getattr(objclass, instruction.argval)
2417
- except Exception:
2418
- ref_obj = None
2419
- if instruction.argval in invalid_names:
2420
- raise VarValueError(
2421
- f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`."
2422
- )
2423
- if callable(ref_obj):
2424
- # recurse into callable attributes
2425
- d.update(
2426
- self._deps(
2427
- objclass=objclass,
2428
- obj=ref_obj,
2429
- )
2430
- )
2431
- # recurse into property fget functions
2432
- elif isinstance(ref_obj, property) and not isinstance(
2433
- ref_obj, ComputedVar
2434
- ):
2435
- d.update(
2436
- self._deps(
2437
- objclass=objclass,
2438
- obj=ref_obj.fget, # type: ignore
2439
- )
2440
- )
2441
- elif (
2442
- instruction.argval in objclass.backend_vars
2443
- or instruction.argval in objclass.vars
2444
- ):
2445
- # var access
2446
- d.add(instruction.argval)
2447
- elif instruction.opname == "LOAD_CONST" and isinstance(
2448
- instruction.argval, CodeType
2449
- ):
2450
- # recurse into nested functions / comprehensions, which can reference
2451
- # instance attributes from the outer scope
2452
- d.update(
2453
- self._deps(
2454
- objclass=objclass,
2455
- obj=instruction.argval,
2456
- self_name=self_name,
2457
- )
2458
- )
2459
- self_is_top_of_stack = False
2460
- return d
2461
-
2462
- def mark_dirty(self, instance) -> None:
2463
- """Mark this ComputedVar as dirty.
2464
-
2465
- Args:
2466
- instance: the state instance that needs to recompute the value.
2467
- """
2468
- with contextlib.suppress(AttributeError):
2469
- delattr(instance, self._cache_attr)
2470
-
2471
- def _determine_var_type(self) -> Type:
2472
- """Get the type of the var.
2473
-
2474
- Returns:
2475
- The type of the var.
2476
- """
2477
- hints = get_type_hints(property.__getattribute__(self, "fget"))
2478
- if "return" in hints:
2479
- return hints["return"]
2480
- return Any
2481
-
2482
-
2483
- def computed_var(
2484
- fget: Callable[[BaseState], Any] | None = None,
2485
- initial_value: Any | types.Unset = types.Unset(),
2486
- cache: bool = False,
2487
- deps: Optional[List[Union[str, Var]]] = None,
2488
- auto_deps: bool = True,
2489
- interval: Optional[Union[datetime.timedelta, int]] = None,
2490
- backend: bool | None = None,
2491
- _deprecated_cached_var: bool = False,
2492
- **kwargs,
2493
- ) -> ComputedVar | Callable[[Callable[[BaseState], Any]], ComputedVar]:
2494
- """A ComputedVar decorator with or without kwargs.
2495
-
2496
- Args:
2497
- fget: The getter function.
2498
- initial_value: The initial value of the computed var.
2499
- cache: Whether to cache the computed value.
2500
- deps: Explicit var dependencies to track.
2501
- auto_deps: Whether var dependencies should be auto-determined.
2502
- interval: Interval at which the computed var should be updated.
2503
- backend: Whether the computed var is a backend var.
2504
- _deprecated_cached_var: Indicate usage of deprecated cached_var partial function.
2505
- **kwargs: additional attributes to set on the instance
2506
-
2507
- Returns:
2508
- A ComputedVar instance.
2509
-
2510
- Raises:
2511
- ValueError: If caching is disabled and an update interval is set.
2512
- VarDependencyError: If user supplies dependencies without caching.
2513
- """
2514
- if _deprecated_cached_var:
2515
- console.deprecate(
2516
- feature_name="cached_var",
2517
- reason=("Use @rx.var(cache=True) instead of @rx.cached_var."),
2518
- deprecation_version="0.5.6",
2519
- removal_version="0.6.0",
2520
- )
2521
-
2522
- if cache is False and interval is not None:
2523
- raise ValueError("Cannot set update interval without caching.")
2524
-
2525
- if cache is False and (deps is not None or auto_deps is False):
2526
- raise VarDependencyError("Cannot track dependencies without caching.")
2527
-
2528
- if fget is not None:
2529
- return ComputedVar(fget=fget, cache=cache)
2530
-
2531
- def wrapper(fget: Callable[[BaseState], Any]) -> ComputedVar:
2532
- return ComputedVar(
2533
- fget=fget,
2534
- initial_value=initial_value,
2535
- cache=cache,
2536
- deps=deps,
2537
- auto_deps=auto_deps,
2538
- interval=interval,
2539
- backend=backend,
2540
- **kwargs,
2541
- )
2542
-
2543
- return wrapper
2544
-
2545
-
2546
- # Partial function of computed_var with cache=True
2547
- cached_var = functools.partial(computed_var, cache=True, _deprecated_cached_var=True)
2548
-
2549
-
2550
- class CallableVar(BaseVar):
2551
- """Decorate a Var-returning function to act as both a Var and a function.
2552
-
2553
- This is used as a compatibility shim for replacing Var objects in the
2554
- API with functions that return a family of Var.
2555
- """
2556
-
2557
- def __init__(self, fn: Callable[..., BaseVar]):
2558
- """Initialize a CallableVar.
2559
-
2560
- Args:
2561
- fn: The function to decorate (must return Var)
2562
- """
2563
- self.fn = fn
2564
- default_var = fn()
2565
- super().__init__(**dataclasses.asdict(default_var))
2566
-
2567
- def __call__(self, *args, **kwargs) -> BaseVar:
2568
- """Call the decorated function.
2569
-
2570
- Args:
2571
- *args: The args to pass to the function.
2572
- **kwargs: The kwargs to pass to the function.
2573
-
2574
- Returns:
2575
- The Var returned from calling the function.
2576
- """
2577
- return self.fn(*args, **kwargs)
2578
-
2579
475
 
2580
- def get_uuid_string_var() -> Var:
476
+ def get_uuid_string_var() -> ImmutableVar:
2581
477
  """Return a Var that generates a single memoized UUID via .web/utils/state.js.
2582
478
 
2583
479
  useMemo with an empty dependency array ensures that the generated UUID is
@@ -2586,6 +482,7 @@ def get_uuid_string_var() -> Var:
2586
482
  Returns:
2587
483
  A Var that generates a UUID at runtime.
2588
484
  """
485
+ from reflex.ivars import ImmutableVar
2589
486
  from reflex.utils.imports import ImportVar
2590
487
 
2591
488
  unique_uuid_var = get_unique_variable_name()
@@ -2597,7 +494,7 @@ def get_uuid_string_var() -> Var:
2597
494
  hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
2598
495
  )
2599
496
 
2600
- return BaseVar(
497
+ return ImmutableVar(
2601
498
  _var_name=unique_uuid_var,
2602
499
  _var_type=str,
2603
500
  _var_data=unique_uuid_var_data,