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