reflex 0.7.1a4__py3-none-any.whl → 0.7.2__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 (227) hide show
  1. reflex/.templates/jinja/web/utils/context.js.jinja2 +8 -8
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  3. reflex/.templates/web/utils/state.js +18 -18
  4. reflex/admin.py +1 -2
  5. reflex/app.py +46 -49
  6. reflex/app_mixins/lifespan.py +2 -2
  7. reflex/app_mixins/middleware.py +1 -2
  8. reflex/assets.py +1 -2
  9. reflex/base.py +2 -2
  10. reflex/compiler/compiler.py +51 -16
  11. reflex/compiler/utils.py +4 -13
  12. reflex/components/base/app_wrap.pyi +7 -7
  13. reflex/components/base/bare.py +3 -3
  14. reflex/components/base/body.pyi +7 -7
  15. reflex/components/base/document.py +1 -3
  16. reflex/components/base/document.pyi +32 -32
  17. reflex/components/base/error_boundary.py +2 -4
  18. reflex/components/base/error_boundary.pyi +11 -13
  19. reflex/components/base/fragment.pyi +7 -7
  20. reflex/components/base/head.pyi +13 -13
  21. reflex/components/base/link.pyi +22 -22
  22. reflex/components/base/meta.py +5 -7
  23. reflex/components/base/meta.pyi +40 -40
  24. reflex/components/base/script.pyi +11 -14
  25. reflex/components/base/strict_mode.pyi +7 -7
  26. reflex/components/component.py +188 -113
  27. reflex/components/core/auto_scroll.py +8 -1
  28. reflex/components/core/auto_scroll.pyi +183 -210
  29. reflex/components/core/banner.py +2 -4
  30. reflex/components/core/banner.pyi +390 -444
  31. reflex/components/core/breakpoints.py +5 -5
  32. reflex/components/core/client_side_routing.pyi +14 -14
  33. reflex/components/core/clipboard.py +4 -4
  34. reflex/components/core/clipboard.pyi +12 -14
  35. reflex/components/core/cond.py +17 -25
  36. reflex/components/core/debounce.py +3 -3
  37. reflex/components/core/debounce.pyi +14 -14
  38. reflex/components/core/foreach.py +7 -2
  39. reflex/components/core/html.py +1 -3
  40. reflex/components/core/html.pyi +184 -213
  41. reflex/components/core/match.py +15 -19
  42. reflex/components/core/sticky.pyi +930 -1078
  43. reflex/components/core/upload.py +4 -4
  44. reflex/components/core/upload.pyi +62 -62
  45. reflex/components/datadisplay/code.py +6 -6
  46. reflex/components/datadisplay/code.pyi +1159 -1165
  47. reflex/components/datadisplay/dataeditor.py +49 -49
  48. reflex/components/datadisplay/dataeditor.pyi +95 -123
  49. reflex/components/datadisplay/logo.py +1 -3
  50. reflex/components/datadisplay/shiki_code_block.py +8 -10
  51. reflex/components/datadisplay/shiki_code_block.pyi +1678 -1720
  52. reflex/components/el/element.pyi +7 -7
  53. reflex/components/el/elements/base.pyi +183 -210
  54. reflex/components/el/elements/forms.py +24 -24
  55. reflex/components/el/elements/forms.pyi +2572 -2934
  56. reflex/components/el/elements/inline.py +4 -4
  57. reflex/components/el/elements/inline.pyi +5191 -5953
  58. reflex/components/el/elements/media.py +47 -47
  59. reflex/components/el/elements/media.pyi +4802 -5500
  60. reflex/components/el/elements/metadata.py +1 -3
  61. reflex/components/el/elements/metadata.pyi +782 -896
  62. reflex/components/el/elements/other.pyi +1278 -1467
  63. reflex/components/el/elements/scripts.pyi +580 -667
  64. reflex/components/el/elements/sectioning.pyi +2761 -3166
  65. reflex/components/el/elements/tables.pyi +1840 -2119
  66. reflex/components/el/elements/typography.pyi +2772 -3179
  67. reflex/components/gridjs/datatable.py +7 -7
  68. reflex/components/gridjs/datatable.pyi +19 -19
  69. reflex/components/lucide/icon.pyi +21 -21
  70. reflex/components/markdown/markdown.py +2 -2
  71. reflex/components/markdown/markdown.pyi +9 -9
  72. reflex/components/moment/moment.py +11 -12
  73. reflex/components/moment/moment.pyi +44 -47
  74. reflex/components/next/base.pyi +7 -7
  75. reflex/components/next/image.py +3 -3
  76. reflex/components/next/image.pyi +19 -21
  77. reflex/components/next/link.pyi +9 -9
  78. reflex/components/next/video.py +1 -3
  79. reflex/components/next/video.pyi +9 -9
  80. reflex/components/plotly/plotly.py +22 -45
  81. reflex/components/plotly/plotly.pyi +164 -164
  82. reflex/components/radix/primitives/accordion.py +14 -14
  83. reflex/components/radix/primitives/accordion.pyi +439 -487
  84. reflex/components/radix/primitives/base.py +1 -3
  85. reflex/components/radix/primitives/base.pyi +15 -15
  86. reflex/components/radix/primitives/drawer.py +3 -3
  87. reflex/components/radix/primitives/drawer.pyi +110 -116
  88. reflex/components/radix/primitives/form.py +1 -1
  89. reflex/components/radix/primitives/form.pyi +668 -752
  90. reflex/components/radix/primitives/progress.py +6 -6
  91. reflex/components/radix/primitives/progress.pyi +225 -243
  92. reflex/components/radix/primitives/slider.py +6 -6
  93. reflex/components/radix/primitives/slider.pyi +52 -55
  94. reflex/components/radix/themes/base.py +3 -6
  95. reflex/components/radix/themes/base.pyi +197 -303
  96. reflex/components/radix/themes/color_mode.py +5 -5
  97. reflex/components/radix/themes/color_mode.pyi +366 -436
  98. reflex/components/radix/themes/components/alert_dialog.pyi +229 -262
  99. reflex/components/radix/themes/components/aspect_ratio.py +1 -3
  100. reflex/components/radix/themes/components/aspect_ratio.pyi +8 -8
  101. reflex/components/radix/themes/components/avatar.pyi +79 -94
  102. reflex/components/radix/themes/components/badge.pyi +252 -295
  103. reflex/components/radix/themes/components/button.pyi +269 -314
  104. reflex/components/radix/themes/components/callout.py +2 -2
  105. reflex/components/radix/themes/components/callout.pyi +1116 -1290
  106. reflex/components/radix/themes/components/card.pyi +194 -229
  107. reflex/components/radix/themes/components/checkbox.pyi +243 -278
  108. reflex/components/radix/themes/components/checkbox_cards.py +3 -7
  109. reflex/components/radix/themes/components/checkbox_cards.pyi +101 -135
  110. reflex/components/radix/themes/components/checkbox_group.py +2 -2
  111. reflex/components/radix/themes/components/checkbox_group.pyi +83 -96
  112. reflex/components/radix/themes/components/context_menu.py +18 -15
  113. reflex/components/radix/themes/components/context_menu.pyi +408 -458
  114. reflex/components/radix/themes/components/data_list.pyi +122 -147
  115. reflex/components/radix/themes/components/dialog.pyi +231 -264
  116. reflex/components/radix/themes/components/dropdown_menu.py +16 -13
  117. reflex/components/radix/themes/components/dropdown_menu.pyi +223 -246
  118. reflex/components/radix/themes/components/hover_card.py +2 -2
  119. reflex/components/radix/themes/components/hover_card.pyi +237 -282
  120. reflex/components/radix/themes/components/icon_button.pyi +269 -314
  121. reflex/components/radix/themes/components/inset.py +8 -8
  122. reflex/components/radix/themes/components/inset.pyi +232 -292
  123. reflex/components/radix/themes/components/popover.py +2 -2
  124. reflex/components/radix/themes/components/popover.pyi +229 -271
  125. reflex/components/radix/themes/components/progress.pyi +80 -96
  126. reflex/components/radix/themes/components/radio.pyi +73 -86
  127. reflex/components/radix/themes/components/radio_cards.py +4 -8
  128. reflex/components/radix/themes/components/radio_cards.pyi +117 -154
  129. reflex/components/radix/themes/components/radio_group.py +3 -3
  130. reflex/components/radix/themes/components/radio_group.pyi +250 -291
  131. reflex/components/radix/themes/components/scroll_area.pyi +14 -20
  132. reflex/components/radix/themes/components/segmented_control.py +6 -6
  133. reflex/components/radix/themes/components/segmented_control.pyi +89 -108
  134. reflex/components/radix/themes/components/select.py +7 -7
  135. reflex/components/radix/themes/components/select.pyi +376 -444
  136. reflex/components/radix/themes/components/separator.pyi +79 -93
  137. reflex/components/radix/themes/components/skeleton.pyi +32 -26
  138. reflex/components/radix/themes/components/slider.py +8 -8
  139. reflex/components/radix/themes/components/slider.pyi +99 -122
  140. reflex/components/radix/themes/components/spinner.pyi +12 -19
  141. reflex/components/radix/themes/components/switch.pyi +84 -99
  142. reflex/components/radix/themes/components/table.py +9 -9
  143. reflex/components/radix/themes/components/table.pyi +1440 -1794
  144. reflex/components/radix/themes/components/tabs.py +4 -4
  145. reflex/components/radix/themes/components/tabs.pyi +120 -132
  146. reflex/components/radix/themes/components/text_area.pyi +281 -331
  147. reflex/components/radix/themes/components/text_field.py +2 -2
  148. reflex/components/radix/themes/components/text_field.pyi +639 -734
  149. reflex/components/radix/themes/components/tooltip.py +6 -6
  150. reflex/components/radix/themes/components/tooltip.pyi +34 -43
  151. reflex/components/radix/themes/layout/base.pyi +85 -182
  152. reflex/components/radix/themes/layout/box.pyi +183 -210
  153. reflex/components/radix/themes/layout/center.pyi +225 -286
  154. reflex/components/radix/themes/layout/container.pyi +191 -224
  155. reflex/components/radix/themes/layout/flex.py +2 -2
  156. reflex/components/radix/themes/layout/flex.pyi +225 -286
  157. reflex/components/radix/themes/layout/grid.py +2 -2
  158. reflex/components/radix/themes/layout/grid.pyi +245 -315
  159. reflex/components/radix/themes/layout/list.py +2 -2
  160. reflex/components/radix/themes/layout/list.pyi +712 -815
  161. reflex/components/radix/themes/layout/section.pyi +187 -221
  162. reflex/components/radix/themes/layout/spacer.pyi +225 -286
  163. reflex/components/radix/themes/layout/stack.pyi +625 -768
  164. reflex/components/radix/themes/typography/blockquote.pyi +257 -299
  165. reflex/components/radix/themes/typography/code.pyi +259 -304
  166. reflex/components/radix/themes/typography/heading.pyi +272 -324
  167. reflex/components/radix/themes/typography/link.pyi +302 -358
  168. reflex/components/radix/themes/typography/text.pyi +1669 -1945
  169. reflex/components/react_player/audio.pyi +20 -22
  170. reflex/components/react_player/react_player.pyi +19 -19
  171. reflex/components/react_player/video.pyi +20 -22
  172. reflex/components/recharts/cartesian.py +100 -97
  173. reflex/components/recharts/cartesian.pyi +891 -1007
  174. reflex/components/recharts/charts.py +42 -42
  175. reflex/components/recharts/charts.pyi +212 -249
  176. reflex/components/recharts/general.py +22 -21
  177. reflex/components/recharts/general.pyi +198 -223
  178. reflex/components/recharts/polar.py +42 -45
  179. reflex/components/recharts/polar.pyi +254 -288
  180. reflex/components/recharts/recharts.pyi +13 -13
  181. reflex/components/sonner/toast.py +20 -20
  182. reflex/components/sonner/toast.pyi +58 -61
  183. reflex/components/suneditor/editor.py +9 -9
  184. reflex/components/suneditor/editor.pyi +78 -83
  185. reflex/components/tags/cond_tag.py +2 -2
  186. reflex/components/tags/iter_tag.py +10 -14
  187. reflex/components/tags/match_tag.py +2 -2
  188. reflex/components/tags/tag.py +10 -10
  189. reflex/config.py +36 -35
  190. reflex/constants/__init__.py +56 -53
  191. reflex/custom_components/custom_components.py +6 -7
  192. reflex/event.py +38 -42
  193. reflex/experimental/client_state.py +2 -4
  194. reflex/experimental/layout.py +2 -2
  195. reflex/experimental/layout.pyi +579 -663
  196. reflex/istate/data.py +4 -5
  197. reflex/middleware/hydrate_middleware.py +2 -2
  198. reflex/middleware/middleware.py +2 -2
  199. reflex/model.py +3 -5
  200. reflex/page.py +2 -2
  201. reflex/reflex.py +9 -10
  202. reflex/state.py +77 -49
  203. reflex/style.py +11 -5
  204. reflex/testing.py +21 -24
  205. reflex/utils/console.py +1 -1
  206. reflex/utils/decorator.py +26 -1
  207. reflex/utils/exec.py +6 -11
  208. reflex/utils/export.py +2 -3
  209. reflex/utils/format.py +4 -4
  210. reflex/utils/imports.py +12 -12
  211. reflex/utils/prerequisites.py +35 -84
  212. reflex/utils/processes.py +5 -5
  213. reflex/utils/pyi_generator.py +33 -22
  214. reflex/utils/serializers.py +60 -15
  215. reflex/utils/types.py +237 -56
  216. reflex/vars/base.py +122 -72
  217. reflex/vars/datetime.py +2 -2
  218. reflex/vars/function.py +52 -55
  219. reflex/vars/number.py +59 -5
  220. reflex/vars/object.py +57 -26
  221. reflex/vars/sequence.py +983 -958
  222. {reflex-0.7.1a4.dist-info → reflex-0.7.2.dist-info}/METADATA +3 -6
  223. reflex-0.7.2.dist-info/RECORD +405 -0
  224. {reflex-0.7.1a4.dist-info → reflex-0.7.2.dist-info}/WHEEL +1 -1
  225. reflex-0.7.1a4.dist-info/RECORD +0 -405
  226. {reflex-0.7.1a4.dist-info → reflex-0.7.2.dist-info}/LICENSE +0 -0
  227. {reflex-0.7.1a4.dist-info → reflex-0.7.2.dist-info}/entry_points.txt +0 -0
reflex/vars/sequence.py CHANGED
@@ -2,23 +2,24 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import collections.abc
5
6
  import dataclasses
6
7
  import inspect
7
8
  import json
8
9
  import re
9
- import typing
10
10
  from typing import (
11
11
  TYPE_CHECKING,
12
12
  Any,
13
- Dict,
13
+ Iterable,
14
14
  List,
15
15
  Literal,
16
+ Mapping,
16
17
  NoReturn,
17
18
  Sequence,
18
- Tuple,
19
19
  Type,
20
20
  TypeVar,
21
21
  Union,
22
+ get_args,
22
23
  overload,
23
24
  )
24
25
 
@@ -27,6 +28,7 @@ from typing_extensions import TypeVar as TypingExtensionsTypeVar
27
28
  from reflex import constants
28
29
  from reflex.constants.base import REFLEX_VAR_OPENING_TAG
29
30
  from reflex.constants.colors import Color
31
+ from reflex.utils import types
30
32
  from reflex.utils.exceptions import VarTypeError
31
33
  from reflex.utils.types import GenericType, get_origin
32
34
 
@@ -58,1279 +60,1275 @@ if TYPE_CHECKING:
58
60
  from .function import FunctionVar
59
61
  from .object import ObjectVar
60
62
 
63
+ ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Sequence, covariant=True)
64
+ OTHER_ARRAY_VAR_TYPE = TypeVar("OTHER_ARRAY_VAR_TYPE", bound=Sequence, covariant=True)
65
+ MAPPING_VAR_TYPE = TypeVar("MAPPING_VAR_TYPE", bound=Mapping, covariant=True)
61
66
 
62
- STRING_TYPE = TypingExtensionsTypeVar("STRING_TYPE", default=str)
67
+ OTHER_TUPLE = TypeVar("OTHER_TUPLE")
68
+
69
+ INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR")
63
70
 
64
71
 
65
- class StringVar(Var[STRING_TYPE], python_types=str):
66
- """Base class for immutable string vars."""
72
+ KEY_TYPE = TypeVar("KEY_TYPE")
73
+ VALUE_TYPE = TypeVar("VALUE_TYPE")
74
+
75
+
76
+ class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(Sequence, set)):
77
+ """Base class for immutable array vars."""
67
78
 
68
79
  @overload
69
- def __add__(self, other: StringVar | str) -> ConcatVarOperation: ...
80
+ def join(self, sep: StringVar | str = "") -> StringVar: ...
70
81
 
71
82
  @overload
72
- def __add__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
83
+ def join(self, sep: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
73
84
 
74
- def __add__(self, other: Any) -> ConcatVarOperation:
75
- """Concatenate two strings.
85
+ def join(self, sep: Any = "") -> StringVar:
86
+ """Join the elements of the array.
76
87
 
77
88
  Args:
78
- other: The other string.
89
+ sep: The separator between elements.
79
90
 
80
91
  Returns:
81
- The string concatenation operation.
92
+ The joined elements.
82
93
  """
83
- if not isinstance(other, (StringVar, str)):
84
- raise_unsupported_operand_types("+", (type(self), type(other)))
94
+ if not isinstance(sep, (StringVar, str)):
95
+ raise_unsupported_operand_types("join", (type(self), type(sep)))
96
+ if (
97
+ isinstance(self, LiteralArrayVar)
98
+ and (
99
+ len(
100
+ args := [
101
+ x
102
+ for x in self._var_value
103
+ if isinstance(x, (LiteralStringVar, str))
104
+ ]
105
+ )
106
+ == len(self._var_value)
107
+ )
108
+ and isinstance(sep, (LiteralStringVar, str))
109
+ ):
110
+ sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep
111
+ return LiteralStringVar.create(
112
+ sep_str.join(
113
+ i._var_value if isinstance(i, LiteralStringVar) else i for i in args
114
+ )
115
+ )
116
+ return array_join_operation(self, sep)
85
117
 
86
- return ConcatVarOperation.create(self, other)
118
+ def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]:
119
+ """Reverse the array.
120
+
121
+ Returns:
122
+ The reversed array.
123
+ """
124
+ return array_reverse_operation(self)
87
125
 
88
126
  @overload
89
- def __radd__(self, other: StringVar | str) -> ConcatVarOperation: ...
127
+ def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]: ...
90
128
 
91
129
  @overload
92
- def __radd__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
130
+ def __add__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
93
131
 
94
- def __radd__(self, other: Any) -> ConcatVarOperation:
95
- """Concatenate two strings.
132
+ def __add__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
133
+ """Concatenate two arrays.
96
134
 
97
- Args:
98
- other: The other string.
135
+ Parameters:
136
+ other: The other array to concatenate.
99
137
 
100
138
  Returns:
101
- The string concatenation operation.
139
+ ArrayConcatOperation: The concatenation of the two arrays.
102
140
  """
103
- if not isinstance(other, (StringVar, str)):
104
- raise_unsupported_operand_types("+", (type(other), type(self)))
141
+ if not isinstance(other, ArrayVar):
142
+ raise_unsupported_operand_types("+", (type(self), type(other)))
105
143
 
106
- return ConcatVarOperation.create(other, self)
144
+ return array_concat_operation(self, other)
107
145
 
108
146
  @overload
109
- def __mul__(self, other: NumberVar | int) -> StringVar: ...
147
+ def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ...
110
148
 
111
149
  @overload
112
- def __mul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
150
+ def __getitem__(
151
+ self: (
152
+ ArrayVar[tuple[int, OTHER_TUPLE]]
153
+ | ArrayVar[tuple[float, OTHER_TUPLE]]
154
+ | ArrayVar[tuple[int | float, OTHER_TUPLE]]
155
+ ),
156
+ i: Literal[0, -2],
157
+ ) -> NumberVar: ...
113
158
 
114
- def __mul__(self, other: Any) -> StringVar:
115
- """Multiply the sequence by a number or an integer.
159
+ @overload
160
+ def __getitem__(
161
+ self: ArrayVar[tuple[Any, bool]], i: Literal[1, -1]
162
+ ) -> BooleanVar: ...
116
163
 
117
- Args:
118
- other: The number or integer to multiply the sequence by.
164
+ @overload
165
+ def __getitem__(
166
+ self: (
167
+ ArrayVar[tuple[Any, int]]
168
+ | ArrayVar[tuple[Any, float]]
169
+ | ArrayVar[tuple[Any, int | float]]
170
+ ),
171
+ i: Literal[1, -1],
172
+ ) -> NumberVar: ...
119
173
 
120
- Returns:
121
- StringVar: The resulting sequence after multiplication.
122
- """
123
- if not isinstance(other, (NumberVar, int)):
124
- raise_unsupported_operand_types("*", (type(self), type(other)))
174
+ @overload
175
+ def __getitem__( # pyright: ignore [reportOverlappingOverload]
176
+ self: ArrayVar[tuple[str, Any]], i: Literal[0, -2]
177
+ ) -> StringVar: ...
125
178
 
126
- return (self.split() * other).join()
179
+ @overload
180
+ def __getitem__(
181
+ self: ArrayVar[tuple[Any, str]], i: Literal[1, -1]
182
+ ) -> StringVar: ...
127
183
 
128
184
  @overload
129
- def __rmul__(self, other: NumberVar | int) -> StringVar: ...
185
+ def __getitem__(
186
+ self: ArrayVar[tuple[bool, Any]], i: Literal[0, -2]
187
+ ) -> BooleanVar: ...
130
188
 
131
189
  @overload
132
- def __rmul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
190
+ def __getitem__(
191
+ self: ArrayVar[Sequence[bool]], i: int | NumberVar
192
+ ) -> BooleanVar: ...
133
193
 
134
- def __rmul__(self, other: Any) -> StringVar:
135
- """Multiply the sequence by a number or an integer.
194
+ @overload
195
+ def __getitem__(
196
+ self: (
197
+ ArrayVar[Sequence[int]]
198
+ | ArrayVar[Sequence[float]]
199
+ | ArrayVar[Sequence[int | float]]
200
+ ),
201
+ i: int | NumberVar,
202
+ ) -> NumberVar: ...
136
203
 
137
- Args:
138
- other: The number or integer to multiply the sequence by.
204
+ @overload
205
+ def __getitem__(self: ArrayVar[Sequence[str]], i: int | NumberVar) -> StringVar: ...
139
206
 
140
- Returns:
141
- StringVar: The resulting sequence after multiplication.
142
- """
143
- if not isinstance(other, (NumberVar, int)):
144
- raise_unsupported_operand_types("*", (type(other), type(self)))
207
+ @overload
208
+ def __getitem__(
209
+ self: ArrayVar[Sequence[OTHER_ARRAY_VAR_TYPE]],
210
+ i: int | NumberVar,
211
+ ) -> ArrayVar[OTHER_ARRAY_VAR_TYPE]: ...
145
212
 
146
- return (self.split() * other).join()
213
+ @overload
214
+ def __getitem__(
215
+ self: ArrayVar[Sequence[MAPPING_VAR_TYPE]],
216
+ i: int | NumberVar,
217
+ ) -> ObjectVar[MAPPING_VAR_TYPE]: ...
147
218
 
148
219
  @overload
149
- def __getitem__(self, i: slice) -> StringVar: ...
220
+ def __getitem__(
221
+ self: ArrayVar[Sequence[BASE_TYPE]],
222
+ i: int | NumberVar,
223
+ ) -> ObjectVar[BASE_TYPE]: ...
150
224
 
151
225
  @overload
152
- def __getitem__(self, i: int | NumberVar) -> StringVar: ...
226
+ def __getitem__(
227
+ self: ArrayVar[Sequence[SQLA_TYPE]],
228
+ i: int | NumberVar,
229
+ ) -> ObjectVar[SQLA_TYPE]: ...
153
230
 
154
- def __getitem__(self, i: Any) -> StringVar:
155
- """Get a slice of the string.
231
+ @overload
232
+ def __getitem__(
233
+ self: ArrayVar[Sequence[DATACLASS_TYPE]],
234
+ i: int | NumberVar,
235
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
236
+
237
+ @overload
238
+ def __getitem__(self, i: int | NumberVar) -> Var: ...
239
+
240
+ def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var:
241
+ """Get a slice of the array.
156
242
 
157
243
  Args:
158
244
  i: The slice.
159
245
 
160
246
  Returns:
161
- The string slice operation.
247
+ The array slice operation.
162
248
  """
163
249
  if isinstance(i, slice):
164
- return self.split()[i].join()
250
+ return ArraySliceOperation.create(self, i)
165
251
  if not isinstance(i, (int, NumberVar)) or (
166
252
  isinstance(i, NumberVar) and i._is_strict_float()
167
253
  ):
168
254
  raise_unsupported_operand_types("[]", (type(self), type(i)))
169
- return string_item_operation(self, i)
255
+ return array_item_operation(self, i)
170
256
 
171
- def length(self) -> NumberVar:
172
- """Get the length of the string.
257
+ def length(self) -> NumberVar[int]:
258
+ """Get the length of the array.
173
259
 
174
260
  Returns:
175
- The string length operation.
261
+ The length of the array.
176
262
  """
177
- return self.split().length()
263
+ return array_length_operation(self)
178
264
 
179
- def lower(self) -> StringVar:
180
- """Convert the string to lowercase.
265
+ @overload
266
+ @classmethod
267
+ def range(cls, stop: int | NumberVar, /) -> ArrayVar[List[int]]: ...
181
268
 
182
- Returns:
183
- The string lower operation.
184
- """
185
- return string_lower_operation(self)
269
+ @overload
270
+ @classmethod
271
+ def range(
272
+ cls,
273
+ start: int | NumberVar,
274
+ end: int | NumberVar,
275
+ step: int | NumberVar = 1,
276
+ /,
277
+ ) -> ArrayVar[List[int]]: ...
186
278
 
187
- def upper(self) -> StringVar:
188
- """Convert the string to uppercase.
279
+ @overload
280
+ @classmethod
281
+ def range(
282
+ cls,
283
+ first_endpoint: int | NumberVar,
284
+ second_endpoint: int | NumberVar | None = None,
285
+ step: int | NumberVar | None = None,
286
+ ) -> ArrayVar[List[int]]: ...
189
287
 
190
- Returns:
191
- The string upper operation.
192
- """
193
- return string_upper_operation(self)
288
+ @classmethod
289
+ def range(
290
+ cls,
291
+ first_endpoint: int | NumberVar,
292
+ second_endpoint: int | NumberVar | None = None,
293
+ step: int | NumberVar | None = None,
294
+ ) -> ArrayVar[List[int]]:
295
+ """Create a range of numbers.
194
296
 
195
- def title(self) -> StringVar:
196
- """Convert the string to title case.
297
+ Args:
298
+ first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
299
+ second_endpoint: The end of the range.
300
+ step: The step of the range.
197
301
 
198
302
  Returns:
199
- The string title operation.
303
+ The range of numbers.
200
304
  """
201
- return string_title_operation(self)
305
+ if any(
306
+ not isinstance(i, (int, NumberVar))
307
+ for i in (first_endpoint, second_endpoint, step)
308
+ if i is not None
309
+ ):
310
+ raise_unsupported_operand_types(
311
+ "range", (type(first_endpoint), type(second_endpoint), type(step))
312
+ )
313
+ if second_endpoint is None:
314
+ start = 0
315
+ end = first_endpoint
316
+ else:
317
+ start = first_endpoint
318
+ end = second_endpoint
202
319
 
203
- def capitalize(self) -> StringVar:
204
- """Capitalize the string.
320
+ return array_range_operation(start, end, step or 1)
205
321
 
206
- Returns:
207
- The string capitalize operation.
208
- """
209
- return string_capitalize_operation(self)
322
+ @overload
323
+ def contains(self, other: Any) -> BooleanVar: ...
210
324
 
211
- def strip(self) -> StringVar:
212
- """Strip the string.
325
+ @overload
326
+ def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ...
213
327
 
214
- Returns:
215
- The string strip operation.
216
- """
217
- return string_strip_operation(self)
218
-
219
- def reversed(self) -> StringVar:
220
- """Reverse the string.
221
-
222
- Returns:
223
- The string reverse operation.
224
- """
225
- return self.split().reverse().join()
226
-
227
- @overload
228
- def contains(
229
- self, other: StringVar | str, field: StringVar | str | None = None
230
- ) -> BooleanVar: ...
231
-
232
- @overload
233
- def contains( # pyright: ignore [reportOverlappingOverload]
234
- self, other: NoReturn, field: StringVar | str | None = None
235
- ) -> NoReturn: ...
236
-
237
- def contains(self, other: Any, field: Any = None) -> BooleanVar:
238
- """Check if the string contains another string.
328
+ def contains(self, other: Any, field: Any = None) -> BooleanVar:
329
+ """Check if the array contains an element.
239
330
 
240
331
  Args:
241
- other: The other string.
332
+ other: The element to check for.
242
333
  field: The field to check.
243
334
 
244
335
  Returns:
245
- The string contains operation.
336
+ The array contains operation.
246
337
  """
247
- if not isinstance(other, (StringVar, str)):
248
- raise_unsupported_operand_types("contains", (type(self), type(other)))
249
338
  if field is not None:
250
339
  if not isinstance(field, (StringVar, str)):
251
340
  raise_unsupported_operand_types("contains", (type(self), type(field)))
252
- return string_contains_field_operation(self, other, field)
253
- return string_contains_operation(self, other)
254
-
255
- @overload
256
- def split(self, separator: StringVar | str = "") -> ArrayVar[List[str]]: ...
257
-
258
- @overload
259
- def split(self, separator: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
341
+ return array_contains_field_operation(self, other, field)
342
+ return array_contains_operation(self, other)
260
343
 
261
- def split(self, separator: Any = "") -> ArrayVar[List[str]]:
262
- """Split the string.
344
+ def pluck(self, field: StringVar | str) -> ArrayVar:
345
+ """Pluck a field from the array.
263
346
 
264
347
  Args:
265
- separator: The separator.
348
+ field: The field to pluck from the array.
266
349
 
267
350
  Returns:
268
- The string split operation.
351
+ The array pluck operation.
269
352
  """
270
- if not isinstance(separator, (StringVar, str)):
271
- raise_unsupported_operand_types("split", (type(self), type(separator)))
272
- return string_split_operation(self, separator)
353
+ return array_pluck_operation(self, field)
273
354
 
274
355
  @overload
275
- def startswith(self, prefix: StringVar | str) -> BooleanVar: ...
356
+ def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]: ...
276
357
 
277
358
  @overload
278
- def startswith(self, prefix: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
359
+ def __mul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
279
360
 
280
- def startswith(self, prefix: Any) -> BooleanVar:
281
- """Check if the string starts with a prefix.
361
+ def __mul__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
362
+ """Multiply the sequence by a number or integer.
282
363
 
283
- Args:
284
- prefix: The prefix.
364
+ Parameters:
365
+ other: The number or integer to multiply the sequence by.
285
366
 
286
367
  Returns:
287
- The string starts with operation.
368
+ ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
288
369
  """
289
- if not isinstance(prefix, (StringVar, str)):
290
- raise_unsupported_operand_types("startswith", (type(self), type(prefix)))
291
- return string_starts_with_operation(self, prefix)
292
-
293
- @overload
294
- def endswith(self, suffix: StringVar | str) -> BooleanVar: ...
295
-
296
- @overload
297
- def endswith(self, suffix: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
298
-
299
- def endswith(self, suffix: Any) -> BooleanVar:
300
- """Check if the string ends with a suffix.
370
+ if not isinstance(other, (NumberVar, int)) or (
371
+ isinstance(other, NumberVar) and other._is_strict_float()
372
+ ):
373
+ raise_unsupported_operand_types("*", (type(self), type(other)))
301
374
 
302
- Args:
303
- suffix: The suffix.
375
+ return repeat_array_operation(self, other)
304
376
 
305
- Returns:
306
- The string ends with operation.
307
- """
308
- if not isinstance(suffix, (StringVar, str)):
309
- raise_unsupported_operand_types("endswith", (type(self), type(suffix)))
310
- return string_ends_with_operation(self, suffix)
377
+ __rmul__ = __mul__
311
378
 
312
379
  @overload
313
- def __lt__(self, other: StringVar | str) -> BooleanVar: ...
380
+ def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
314
381
 
315
382
  @overload
316
- def __lt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
383
+ def __lt__(self, other: list | tuple) -> BooleanVar: ...
317
384
 
318
385
  def __lt__(self, other: Any):
319
- """Check if the string is less than another string.
386
+ """Check if the array is less than another array.
320
387
 
321
388
  Args:
322
- other: The other string.
389
+ other: The other array.
323
390
 
324
391
  Returns:
325
- The string less than operation.
392
+ The array less than operation.
326
393
  """
327
- if not isinstance(other, (StringVar, str)):
394
+ if not isinstance(other, (ArrayVar, list, tuple)):
328
395
  raise_unsupported_operand_types("<", (type(self), type(other)))
329
396
 
330
- return string_lt_operation(self, other)
397
+ return array_lt_operation(self, other)
331
398
 
332
399
  @overload
333
- def __gt__(self, other: StringVar | str) -> BooleanVar: ...
400
+ def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
334
401
 
335
402
  @overload
336
- def __gt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
403
+ def __gt__(self, other: list | tuple) -> BooleanVar: ...
337
404
 
338
405
  def __gt__(self, other: Any):
339
- """Check if the string is greater than another string.
406
+ """Check if the array is greater than another array.
340
407
 
341
408
  Args:
342
- other: The other string.
409
+ other: The other array.
343
410
 
344
411
  Returns:
345
- The string greater than operation.
412
+ The array greater than operation.
346
413
  """
347
- if not isinstance(other, (StringVar, str)):
414
+ if not isinstance(other, (ArrayVar, list, tuple)):
348
415
  raise_unsupported_operand_types(">", (type(self), type(other)))
349
416
 
350
- return string_gt_operation(self, other)
417
+ return array_gt_operation(self, other)
351
418
 
352
419
  @overload
353
- def __le__(self, other: StringVar | str) -> BooleanVar: ...
420
+ def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
354
421
 
355
422
  @overload
356
- def __le__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
423
+ def __le__(self, other: list | tuple) -> BooleanVar: ...
357
424
 
358
425
  def __le__(self, other: Any):
359
- """Check if the string is less than or equal to another string.
426
+ """Check if the array is less than or equal to another array.
360
427
 
361
428
  Args:
362
- other: The other string.
429
+ other: The other array.
363
430
 
364
431
  Returns:
365
- The string less than or equal operation.
432
+ The array less than or equal operation.
366
433
  """
367
- if not isinstance(other, (StringVar, str)):
434
+ if not isinstance(other, (ArrayVar, list, tuple)):
368
435
  raise_unsupported_operand_types("<=", (type(self), type(other)))
369
436
 
370
- return string_le_operation(self, other)
437
+ return array_le_operation(self, other)
371
438
 
372
439
  @overload
373
- def __ge__(self, other: StringVar | str) -> BooleanVar: ...
440
+ def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
374
441
 
375
442
  @overload
376
- def __ge__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
443
+ def __ge__(self, other: list | tuple) -> BooleanVar: ...
377
444
 
378
445
  def __ge__(self, other: Any):
379
- """Check if the string is greater than or equal to another string.
446
+ """Check if the array is greater than or equal to another array.
380
447
 
381
448
  Args:
382
- other: The other string.
449
+ other: The other array.
383
450
 
384
451
  Returns:
385
- The string greater than or equal operation.
452
+ The array greater than or equal operation.
386
453
  """
387
- if not isinstance(other, (StringVar, str)):
454
+ if not isinstance(other, (ArrayVar, list, tuple)):
388
455
  raise_unsupported_operand_types(">=", (type(self), type(other)))
389
456
 
390
- return string_ge_operation(self, other)
391
-
392
- @overload
393
- def replace( # pyright: ignore [reportOverlappingOverload]
394
- self, search_value: StringVar | str, new_value: StringVar | str
395
- ) -> StringVar: ...
396
-
397
- @overload
398
- def replace(
399
- self, search_value: Any, new_value: Any
400
- ) -> CustomVarOperationReturn[StringVar]: ...
457
+ return array_ge_operation(self, other)
401
458
 
402
- def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload]
403
- """Replace a string with a value.
459
+ def foreach(self, fn: Any):
460
+ """Apply a function to each element of the array.
404
461
 
405
462
  Args:
406
- search_value: The string to search.
407
- new_value: The value to be replaced with.
463
+ fn: The function to apply.
408
464
 
409
465
  Returns:
410
- The string replace operation.
411
- """
412
- if not isinstance(search_value, (StringVar, str)):
413
- raise_unsupported_operand_types("replace", (type(self), type(search_value)))
414
- if not isinstance(new_value, (StringVar, str)):
415
- raise_unsupported_operand_types("replace", (type(self), type(new_value)))
416
-
417
- return string_replace_operation(self, search_value, new_value)
466
+ The array after applying the function.
418
467
 
468
+ Raises:
469
+ VarTypeError: If the function takes more than one argument.
470
+ """
471
+ from .function import ArgsFunctionOperation
419
472
 
420
- @var_operation
421
- def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
422
- """Check if a string is less than another string.
473
+ if not callable(fn):
474
+ raise_unsupported_operand_types("foreach", (type(self), type(fn)))
475
+ # get the number of arguments of the function
476
+ num_args = len(inspect.signature(fn).parameters)
477
+ if num_args > 1:
478
+ raise VarTypeError(
479
+ "The function passed to foreach should take at most one argument."
480
+ )
423
481
 
424
- Args:
425
- lhs: The left-hand side string.
426
- rhs: The right-hand side string.
482
+ if num_args == 0:
483
+ return_value = fn()
484
+ function_var = ArgsFunctionOperation.create((), return_value)
485
+ else:
486
+ # generic number var
487
+ number_var = Var("").to(NumberVar, int)
427
488
 
428
- Returns:
429
- The string less than operation.
430
- """
431
- return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool)
489
+ first_arg_type = self[number_var]._var_type
432
490
 
491
+ arg_name = get_unique_variable_name()
433
492
 
434
- @var_operation
435
- def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
436
- """Check if a string is greater than another string.
493
+ # get first argument type
494
+ first_arg = Var(
495
+ _js_expr=arg_name,
496
+ _var_type=first_arg_type,
497
+ ).guess_type()
437
498
 
438
- Args:
439
- lhs: The left-hand side string.
440
- rhs: The right-hand side string.
499
+ function_var = ArgsFunctionOperation.create(
500
+ (arg_name,),
501
+ Var.create(fn(first_arg)),
502
+ )
441
503
 
442
- Returns:
443
- The string greater than operation.
444
- """
445
- return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool)
504
+ return map_array_operation(self, function_var)
446
505
 
447
506
 
448
- @var_operation
449
- def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
450
- """Check if a string is less than or equal to another string.
507
+ @dataclasses.dataclass(
508
+ eq=False,
509
+ frozen=True,
510
+ slots=True,
511
+ )
512
+ class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
513
+ """Base class for immutable literal array vars."""
451
514
 
452
- Args:
453
- lhs: The left-hand side string.
454
- rhs: The right-hand side string.
515
+ _var_value: Sequence[Union[Var, Any]] = dataclasses.field(default=())
455
516
 
456
- Returns:
457
- The string less than or equal operation.
458
- """
459
- return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool)
517
+ @cached_property_no_lock
518
+ def _cached_var_name(self) -> str:
519
+ """The name of the var.
460
520
 
521
+ Returns:
522
+ The name of the var.
523
+ """
524
+ return (
525
+ "["
526
+ + ", ".join(
527
+ [str(LiteralVar.create(element)) for element in self._var_value]
528
+ )
529
+ + "]"
530
+ )
461
531
 
462
- @var_operation
463
- def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
464
- """Check if a string is greater than or equal to another string.
532
+ @cached_property_no_lock
533
+ def _cached_get_all_var_data(self) -> VarData | None:
534
+ """Get all the VarData associated with the Var.
465
535
 
466
- Args:
467
- lhs: The left-hand side string.
468
- rhs: The right-hand side string.
469
-
470
- Returns:
471
- The string greater than or equal operation.
472
- """
473
- return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool)
536
+ Returns:
537
+ The VarData associated with the Var.
538
+ """
539
+ return VarData.merge(
540
+ *[
541
+ LiteralVar.create(element)._get_all_var_data()
542
+ for element in self._var_value
543
+ ],
544
+ self._var_data,
545
+ )
474
546
 
547
+ def __hash__(self) -> int:
548
+ """Get the hash of the var.
475
549
 
476
- @var_operation
477
- def string_lower_operation(string: StringVar[Any]):
478
- """Convert a string to lowercase.
550
+ Returns:
551
+ The hash of the var.
552
+ """
553
+ return hash((self.__class__.__name__, self._js_expr))
479
554
 
480
- Args:
481
- string: The string to convert.
555
+ def json(self) -> str:
556
+ """Get the JSON representation of the var.
482
557
 
483
- Returns:
484
- The lowercase string.
485
- """
486
- return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str)
558
+ Returns:
559
+ The JSON representation of the var.
487
560
 
561
+ Raises:
562
+ TypeError: If the array elements are not of type LiteralVar.
563
+ """
564
+ elements = []
565
+ for element in self._var_value:
566
+ element_var = LiteralVar.create(element)
567
+ if not isinstance(element_var, LiteralVar):
568
+ raise TypeError(
569
+ f"Array elements must be of type LiteralVar, not {type(element_var)}"
570
+ )
571
+ elements.append(element_var.json())
488
572
 
489
- @var_operation
490
- def string_upper_operation(string: StringVar[Any]):
491
- """Convert a string to uppercase.
573
+ return "[" + ", ".join(elements) + "]"
492
574
 
493
- Args:
494
- string: The string to convert.
575
+ @classmethod
576
+ def create(
577
+ cls,
578
+ value: OTHER_ARRAY_VAR_TYPE,
579
+ _var_type: Type[OTHER_ARRAY_VAR_TYPE] | None = None,
580
+ _var_data: VarData | None = None,
581
+ ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]:
582
+ """Create a var from a string value.
495
583
 
496
- Returns:
497
- The uppercase string.
498
- """
499
- return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str)
584
+ Args:
585
+ value: The value to create the var from.
586
+ _var_type: The type of the var.
587
+ _var_data: Additional hooks and imports associated with the Var.
500
588
 
589
+ Returns:
590
+ The var.
591
+ """
592
+ return LiteralArrayVar(
593
+ _js_expr="",
594
+ _var_type=figure_out_type(value) if _var_type is None else _var_type,
595
+ _var_data=_var_data,
596
+ _var_value=value,
597
+ )
501
598
 
502
- @var_operation
503
- def string_title_operation(string: StringVar[Any]):
504
- """Convert a string to title case.
505
599
 
506
- Args:
507
- string: The string to convert.
600
+ STRING_TYPE = TypingExtensionsTypeVar("STRING_TYPE", default=str)
508
601
 
509
- Returns:
510
- The title case string.
511
- """
512
- return var_operation_return(
513
- js_expression=f"{string}.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')",
514
- var_type=str,
515
- )
516
602
 
603
+ class StringVar(Var[STRING_TYPE], python_types=str):
604
+ """Base class for immutable string vars."""
517
605
 
518
- @var_operation
519
- def string_capitalize_operation(string: StringVar[Any]):
520
- """Capitalize a string.
606
+ @overload
607
+ def __add__(self, other: StringVar | str) -> ConcatVarOperation: ...
521
608
 
522
- Args:
523
- string: The string to capitalize.
609
+ @overload
610
+ def __add__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
524
611
 
525
- Returns:
526
- The capitalized string.
527
- """
528
- return var_operation_return(
529
- js_expression=f"(((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())({string}))",
530
- var_type=str,
531
- )
612
+ def __add__(self, other: Any) -> ConcatVarOperation:
613
+ """Concatenate two strings.
532
614
 
615
+ Args:
616
+ other: The other string.
533
617
 
534
- @var_operation
535
- def string_strip_operation(string: StringVar[Any]):
536
- """Strip a string.
618
+ Returns:
619
+ The string concatenation operation.
620
+ """
621
+ if not isinstance(other, (StringVar, str)):
622
+ raise_unsupported_operand_types("+", (type(self), type(other)))
537
623
 
538
- Args:
539
- string: The string to strip.
624
+ return ConcatVarOperation.create(self, other)
540
625
 
541
- Returns:
542
- The stripped string.
543
- """
544
- return var_operation_return(js_expression=f"{string}.trim()", var_type=str)
626
+ @overload
627
+ def __radd__(self, other: StringVar | str) -> ConcatVarOperation: ...
545
628
 
629
+ @overload
630
+ def __radd__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
546
631
 
547
- @var_operation
548
- def string_contains_field_operation(
549
- haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str
550
- ):
551
- """Check if a string contains another string.
632
+ def __radd__(self, other: Any) -> ConcatVarOperation:
633
+ """Concatenate two strings.
552
634
 
553
- Args:
554
- haystack: The haystack.
555
- needle: The needle.
556
- field: The field to check.
635
+ Args:
636
+ other: The other string.
557
637
 
558
- Returns:
559
- The string contains operation.
560
- """
561
- return var_operation_return(
562
- js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})",
563
- var_type=bool,
564
- )
638
+ Returns:
639
+ The string concatenation operation.
640
+ """
641
+ if not isinstance(other, (StringVar, str)):
642
+ raise_unsupported_operand_types("+", (type(other), type(self)))
565
643
 
644
+ return ConcatVarOperation.create(other, self)
566
645
 
567
- @var_operation
568
- def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str):
569
- """Check if a string contains another string.
646
+ @overload
647
+ def __mul__(self, other: NumberVar | int) -> StringVar: ...
570
648
 
571
- Args:
572
- haystack: The haystack.
573
- needle: The needle.
649
+ @overload
650
+ def __mul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
574
651
 
575
- Returns:
576
- The string contains operation.
577
- """
578
- return var_operation_return(
579
- js_expression=f"{haystack}.includes({needle})", var_type=bool
580
- )
652
+ def __mul__(self, other: Any) -> StringVar:
653
+ """Multiply the sequence by a number or an integer.
581
654
 
655
+ Args:
656
+ other: The number or integer to multiply the sequence by.
582
657
 
583
- @var_operation
584
- def string_starts_with_operation(
585
- full_string: StringVar[Any], prefix: StringVar[Any] | str
586
- ):
587
- """Check if a string starts with a prefix.
658
+ Returns:
659
+ StringVar: The resulting sequence after multiplication.
660
+ """
661
+ if not isinstance(other, (NumberVar, int)):
662
+ raise_unsupported_operand_types("*", (type(self), type(other)))
588
663
 
589
- Args:
590
- full_string: The full string.
591
- prefix: The prefix.
664
+ return (self.split() * other).join()
592
665
 
593
- Returns:
594
- Whether the string starts with the prefix.
595
- """
596
- return var_operation_return(
597
- js_expression=f"{full_string}.startsWith({prefix})", var_type=bool
598
- )
666
+ @overload
667
+ def __rmul__(self, other: NumberVar | int) -> StringVar: ...
599
668
 
669
+ @overload
670
+ def __rmul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
600
671
 
601
- @var_operation
602
- def string_ends_with_operation(
603
- full_string: StringVar[Any], suffix: StringVar[Any] | str
604
- ):
605
- """Check if a string ends with a suffix.
672
+ def __rmul__(self, other: Any) -> StringVar:
673
+ """Multiply the sequence by a number or an integer.
606
674
 
607
- Args:
608
- full_string: The full string.
609
- suffix: The suffix.
675
+ Args:
676
+ other: The number or integer to multiply the sequence by.
610
677
 
611
- Returns:
612
- Whether the string ends with the suffix.
613
- """
614
- return var_operation_return(
615
- js_expression=f"{full_string}.endsWith({suffix})", var_type=bool
616
- )
678
+ Returns:
679
+ StringVar: The resulting sequence after multiplication.
680
+ """
681
+ if not isinstance(other, (NumberVar, int)):
682
+ raise_unsupported_operand_types("*", (type(other), type(self)))
617
683
 
684
+ return (self.split() * other).join()
618
685
 
619
- @var_operation
620
- def string_item_operation(string: StringVar[Any], index: NumberVar | int):
621
- """Get an item from a string.
686
+ @overload
687
+ def __getitem__(self, i: slice) -> StringVar: ...
622
688
 
623
- Args:
624
- string: The string.
625
- index: The index of the item.
689
+ @overload
690
+ def __getitem__(self, i: int | NumberVar) -> StringVar: ...
626
691
 
627
- Returns:
628
- The item from the string.
629
- """
630
- return var_operation_return(js_expression=f"{string}.at({index})", var_type=str)
692
+ def __getitem__(self, i: Any) -> StringVar:
693
+ """Get a slice of the string.
631
694
 
695
+ Args:
696
+ i: The slice.
632
697
 
633
- @var_operation
634
- def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
635
- """Join the elements of an array.
698
+ Returns:
699
+ The string slice operation.
700
+ """
701
+ if isinstance(i, slice):
702
+ return self.split()[i].join()
703
+ if not isinstance(i, (int, NumberVar)) or (
704
+ isinstance(i, NumberVar) and i._is_strict_float()
705
+ ):
706
+ raise_unsupported_operand_types("[]", (type(self), type(i)))
707
+ return string_item_operation(self, i)
636
708
 
637
- Args:
638
- array: The array.
639
- sep: The separator.
709
+ def length(self) -> NumberVar:
710
+ """Get the length of the string.
640
711
 
641
- Returns:
642
- The joined elements.
643
- """
644
- return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str)
712
+ Returns:
713
+ The string length operation.
714
+ """
715
+ return self.split().length()
645
716
 
717
+ def lower(self) -> StringVar:
718
+ """Convert the string to lowercase.
646
719
 
647
- @var_operation
648
- def string_replace_operation(
649
- string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str
650
- ):
651
- """Replace a string with a value.
720
+ Returns:
721
+ The string lower operation.
722
+ """
723
+ return string_lower_operation(self)
652
724
 
653
- Args:
654
- string: The string.
655
- search_value: The string to search.
656
- new_value: The value to be replaced with.
725
+ def upper(self) -> StringVar:
726
+ """Convert the string to uppercase.
657
727
 
658
- Returns:
659
- The string replace operation.
660
- """
661
- return var_operation_return(
662
- js_expression=f"{string}.replaceAll({search_value}, {new_value})",
663
- var_type=str,
664
- )
728
+ Returns:
729
+ The string upper operation.
730
+ """
731
+ return string_upper_operation(self)
665
732
 
733
+ def title(self) -> StringVar:
734
+ """Convert the string to title case.
666
735
 
667
- # Compile regex for finding reflex var tags.
668
- _decode_var_pattern_re = (
669
- rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
670
- )
671
- _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
736
+ Returns:
737
+ The string title operation.
738
+ """
739
+ return string_title_operation(self)
672
740
 
741
+ def capitalize(self) -> StringVar:
742
+ """Capitalize the string.
673
743
 
674
- @dataclasses.dataclass(
675
- eq=False,
676
- frozen=True,
677
- slots=True,
678
- )
679
- class LiteralStringVar(LiteralVar, StringVar[str]):
680
- """Base class for immutable literal string vars."""
744
+ Returns:
745
+ The string capitalize operation.
746
+ """
747
+ return string_capitalize_operation(self)
681
748
 
682
- _var_value: str = dataclasses.field(default="")
749
+ def strip(self) -> StringVar:
750
+ """Strip the string.
683
751
 
684
- @classmethod
685
- def create(
686
- cls,
687
- value: str,
688
- _var_type: GenericType | None = None,
689
- _var_data: VarData | None = None,
690
- ) -> StringVar:
691
- """Create a var from a string value.
752
+ Returns:
753
+ The string strip operation.
754
+ """
755
+ return string_strip_operation(self)
692
756
 
693
- Args:
694
- value: The value to create the var from.
695
- _var_type: The type of the var.
696
- _var_data: Additional hooks and imports associated with the Var.
757
+ def reversed(self) -> StringVar:
758
+ """Reverse the string.
697
759
 
698
760
  Returns:
699
- The var.
761
+ The string reverse operation.
700
762
  """
701
- # Determine var type in case the value is inherited from str.
702
- _var_type = _var_type or type(value) or str
763
+ return self.split().reverse().join()
703
764
 
704
- if REFLEX_VAR_OPENING_TAG in value:
705
- strings_and_vals: list[Var | str] = []
706
- offset = 0
765
+ @overload
766
+ def contains(
767
+ self, other: StringVar | str, field: StringVar | str | None = None
768
+ ) -> BooleanVar: ...
707
769
 
708
- # Find all tags
709
- while m := _decode_var_pattern.search(value):
710
- start, end = m.span()
770
+ @overload
771
+ def contains( # pyright: ignore [reportOverlappingOverload]
772
+ self, other: NoReturn, field: StringVar | str | None = None
773
+ ) -> NoReturn: ...
711
774
 
712
- strings_and_vals.append(value[:start])
775
+ def contains(self, other: Any, field: Any = None) -> BooleanVar:
776
+ """Check if the string contains another string.
713
777
 
714
- serialized_data = m.group(1)
778
+ Args:
779
+ other: The other string.
780
+ field: The field to check.
715
781
 
716
- if serialized_data.isnumeric() or (
717
- serialized_data[0] == "-" and serialized_data[1:].isnumeric()
718
- ):
719
- # This is a global immutable var.
720
- var = _global_vars[int(serialized_data)]
721
- strings_and_vals.append(var)
722
- value = value[(end + len(var._js_expr)) :]
782
+ Returns:
783
+ The string contains operation.
784
+ """
785
+ if not isinstance(other, (StringVar, str)):
786
+ raise_unsupported_operand_types("contains", (type(self), type(other)))
787
+ if field is not None:
788
+ if not isinstance(field, (StringVar, str)):
789
+ raise_unsupported_operand_types("contains", (type(self), type(field)))
790
+ return string_contains_field_operation(self, other, field)
791
+ return string_contains_operation(self, other)
723
792
 
724
- offset += end - start
793
+ @overload
794
+ def split(self, separator: StringVar | str = "") -> ArrayVar[list[str]]: ...
725
795
 
726
- strings_and_vals.append(value)
796
+ @overload
797
+ def split(self, separator: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
727
798
 
728
- filtered_strings_and_vals = [
729
- s for s in strings_and_vals if isinstance(s, Var) or s
730
- ]
731
- if len(filtered_strings_and_vals) == 1:
732
- only_string = filtered_strings_and_vals[0]
733
- if isinstance(only_string, str):
734
- return LiteralVar.create(only_string).to(StringVar, _var_type)
735
- else:
736
- return only_string.to(StringVar, only_string._var_type)
799
+ def split(self, separator: Any = "") -> ArrayVar[list[str]]:
800
+ """Split the string.
737
801
 
738
- if len(
739
- literal_strings := [
740
- s
741
- for s in filtered_strings_and_vals
742
- if isinstance(s, (str, LiteralStringVar))
743
- ]
744
- ) == len(filtered_strings_and_vals):
745
- return LiteralStringVar.create(
746
- "".join(
747
- s._var_value if isinstance(s, LiteralStringVar) else s
748
- for s in literal_strings
749
- ),
750
- _var_type=_var_type,
751
- _var_data=VarData.merge(
752
- _var_data,
753
- *(
754
- s._get_all_var_data()
755
- for s in filtered_strings_and_vals
756
- if isinstance(s, Var)
757
- ),
758
- ),
759
- )
802
+ Args:
803
+ separator: The separator.
760
804
 
761
- concat_result = ConcatVarOperation.create(
762
- *filtered_strings_and_vals,
763
- _var_data=_var_data,
764
- )
805
+ Returns:
806
+ The string split operation.
807
+ """
808
+ if not isinstance(separator, (StringVar, str)):
809
+ raise_unsupported_operand_types("split", (type(self), type(separator)))
810
+ return string_split_operation(self, separator)
765
811
 
766
- return (
767
- concat_result
768
- if _var_type is str
769
- else concat_result.to(StringVar, _var_type)
770
- )
812
+ @overload
813
+ def startswith(self, prefix: StringVar | str) -> BooleanVar: ...
771
814
 
772
- return LiteralStringVar(
773
- _js_expr=json.dumps(value),
774
- _var_type=_var_type,
775
- _var_data=_var_data,
776
- _var_value=value,
777
- )
815
+ @overload
816
+ def startswith(self, prefix: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
778
817
 
779
- def __hash__(self) -> int:
780
- """Get the hash of the var.
818
+ def startswith(self, prefix: Any) -> BooleanVar:
819
+ """Check if the string starts with a prefix.
820
+
821
+ Args:
822
+ prefix: The prefix.
781
823
 
782
824
  Returns:
783
- The hash of the var.
825
+ The string starts with operation.
784
826
  """
785
- return hash((type(self).__name__, self._var_value))
827
+ if not isinstance(prefix, (StringVar, str)):
828
+ raise_unsupported_operand_types("startswith", (type(self), type(prefix)))
829
+ return string_starts_with_operation(self, prefix)
786
830
 
787
- def json(self) -> str:
788
- """Get the JSON representation of the var.
831
+ @overload
832
+ def endswith(self, suffix: StringVar | str) -> BooleanVar: ...
833
+
834
+ @overload
835
+ def endswith(self, suffix: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
836
+
837
+ def endswith(self, suffix: Any) -> BooleanVar:
838
+ """Check if the string ends with a suffix.
839
+
840
+ Args:
841
+ suffix: The suffix.
789
842
 
790
843
  Returns:
791
- The JSON representation of the var.
844
+ The string ends with operation.
792
845
  """
793
- return json.dumps(self._var_value)
846
+ if not isinstance(suffix, (StringVar, str)):
847
+ raise_unsupported_operand_types("endswith", (type(self), type(suffix)))
848
+ return string_ends_with_operation(self, suffix)
794
849
 
850
+ @overload
851
+ def __lt__(self, other: StringVar | str) -> BooleanVar: ...
795
852
 
796
- @dataclasses.dataclass(
797
- eq=False,
798
- frozen=True,
799
- slots=True,
800
- )
801
- class ConcatVarOperation(CachedVarOperation, StringVar[str]):
802
- """Representing a concatenation of literal string vars."""
853
+ @overload
854
+ def __lt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
803
855
 
804
- _var_value: Tuple[Var, ...] = dataclasses.field(default_factory=tuple)
856
+ def __lt__(self, other: Any):
857
+ """Check if the string is less than another string.
805
858
 
806
- @cached_property_no_lock
807
- def _cached_var_name(self) -> str:
808
- """The name of the var.
859
+ Args:
860
+ other: The other string.
809
861
 
810
862
  Returns:
811
- The name of the var.
863
+ The string less than operation.
812
864
  """
813
- list_of_strs: List[Union[str, Var]] = []
814
- last_string = ""
815
- for var in self._var_value:
816
- if isinstance(var, LiteralStringVar):
817
- last_string += var._var_value
818
- else:
819
- if last_string:
820
- list_of_strs.append(last_string)
821
- last_string = ""
822
- list_of_strs.append(var)
865
+ if not isinstance(other, (StringVar, str)):
866
+ raise_unsupported_operand_types("<", (type(self), type(other)))
823
867
 
824
- if last_string:
825
- list_of_strs.append(last_string)
826
-
827
- list_of_strs_filtered = [
828
- str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s
829
- ]
830
-
831
- if len(list_of_strs_filtered) == 1:
832
- return list_of_strs_filtered[0]
833
-
834
- return "(" + "+".join(list_of_strs_filtered) + ")"
868
+ return string_lt_operation(self, other)
835
869
 
836
- @cached_property_no_lock
837
- def _cached_get_all_var_data(self) -> VarData | None:
838
- """Get all the VarData asVarDatae Var.
870
+ @overload
871
+ def __gt__(self, other: StringVar | str) -> BooleanVar: ...
839
872
 
840
- Returns:
841
- The VarData associated with the Var.
842
- """
843
- return VarData.merge(
844
- *[
845
- var._get_all_var_data()
846
- for var in self._var_value
847
- if isinstance(var, Var)
848
- ],
849
- self._var_data,
850
- )
873
+ @overload
874
+ def __gt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
851
875
 
852
- @classmethod
853
- def create(
854
- cls,
855
- *value: Var | str,
856
- _var_data: VarData | None = None,
857
- ) -> ConcatVarOperation:
858
- """Create a var from a string value.
876
+ def __gt__(self, other: Any):
877
+ """Check if the string is greater than another string.
859
878
 
860
879
  Args:
861
- *value: The values to concatenate.
862
- _var_data: Additional hooks and imports associated with the Var.
880
+ other: The other string.
863
881
 
864
882
  Returns:
865
- The var.
883
+ The string greater than operation.
866
884
  """
867
- return cls(
868
- _js_expr="",
869
- _var_type=str,
870
- _var_data=_var_data,
871
- _var_value=tuple(map(LiteralVar.create, value)),
872
- )
885
+ if not isinstance(other, (StringVar, str)):
886
+ raise_unsupported_operand_types(">", (type(self), type(other)))
873
887
 
888
+ return string_gt_operation(self, other)
874
889
 
875
- ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Sequence, covariant=True)
876
- OTHER_ARRAY_VAR_TYPE = TypeVar("OTHER_ARRAY_VAR_TYPE", bound=Sequence)
890
+ @overload
891
+ def __le__(self, other: StringVar | str) -> BooleanVar: ...
877
892
 
878
- OTHER_TUPLE = TypeVar("OTHER_TUPLE")
893
+ @overload
894
+ def __le__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
879
895
 
880
- INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR")
896
+ def __le__(self, other: Any):
897
+ """Check if the string is less than or equal to another string.
881
898
 
882
- KEY_TYPE = TypeVar("KEY_TYPE")
883
- VALUE_TYPE = TypeVar("VALUE_TYPE")
899
+ Args:
900
+ other: The other string.
884
901
 
902
+ Returns:
903
+ The string less than or equal operation.
904
+ """
905
+ if not isinstance(other, (StringVar, str)):
906
+ raise_unsupported_operand_types("<=", (type(self), type(other)))
885
907
 
886
- class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
887
- """Base class for immutable array vars."""
908
+ return string_le_operation(self, other)
888
909
 
889
910
  @overload
890
- def join(self, sep: StringVar | str = "") -> StringVar: ...
911
+ def __ge__(self, other: StringVar | str) -> BooleanVar: ...
891
912
 
892
913
  @overload
893
- def join(self, sep: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
914
+ def __ge__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
894
915
 
895
- def join(self, sep: Any = "") -> StringVar:
896
- """Join the elements of the array.
916
+ def __ge__(self, other: Any):
917
+ """Check if the string is greater than or equal to another string.
897
918
 
898
919
  Args:
899
- sep: The separator between elements.
920
+ other: The other string.
900
921
 
901
922
  Returns:
902
- The joined elements.
923
+ The string greater than or equal operation.
903
924
  """
904
- if not isinstance(sep, (StringVar, str)):
905
- raise_unsupported_operand_types("join", (type(self), type(sep)))
906
- if (
907
- isinstance(self, LiteralArrayVar)
908
- and (
909
- len(
910
- args := [
911
- x
912
- for x in self._var_value
913
- if isinstance(x, (LiteralStringVar, str))
914
- ]
915
- )
916
- == len(self._var_value)
917
- )
918
- and isinstance(sep, (LiteralStringVar, str))
919
- ):
920
- sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep
921
- return LiteralStringVar.create(
922
- sep_str.join(
923
- i._var_value if isinstance(i, LiteralStringVar) else i for i in args
924
- )
925
- )
926
- return array_join_operation(self, sep)
927
-
928
- def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]:
929
- """Reverse the array.
925
+ if not isinstance(other, (StringVar, str)):
926
+ raise_unsupported_operand_types(">=", (type(self), type(other)))
930
927
 
931
- Returns:
932
- The reversed array.
933
- """
934
- return array_reverse_operation(self)
928
+ return string_ge_operation(self, other)
935
929
 
936
930
  @overload
937
- def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]: ...
931
+ def replace( # pyright: ignore [reportOverlappingOverload]
932
+ self, search_value: StringVar | str, new_value: StringVar | str
933
+ ) -> StringVar: ...
938
934
 
939
935
  @overload
940
- def __add__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
936
+ def replace(
937
+ self, search_value: Any, new_value: Any
938
+ ) -> CustomVarOperationReturn[StringVar]: ...
941
939
 
942
- def __add__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
943
- """Concatenate two arrays.
940
+ def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload]
941
+ """Replace a string with a value.
944
942
 
945
- Parameters:
946
- other: The other array to concatenate.
943
+ Args:
944
+ search_value: The string to search.
945
+ new_value: The value to be replaced with.
947
946
 
948
947
  Returns:
949
- ArrayConcatOperation: The concatenation of the two arrays.
948
+ The string replace operation.
950
949
  """
951
- if not isinstance(other, ArrayVar):
952
- raise_unsupported_operand_types("+", (type(self), type(other)))
950
+ if not isinstance(search_value, (StringVar, str)):
951
+ raise_unsupported_operand_types("replace", (type(self), type(search_value)))
952
+ if not isinstance(new_value, (StringVar, str)):
953
+ raise_unsupported_operand_types("replace", (type(self), type(new_value)))
953
954
 
954
- return array_concat_operation(self, other)
955
+ return string_replace_operation(self, search_value, new_value)
955
956
 
956
- @overload
957
- def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ...
958
957
 
959
- @overload
960
- def __getitem__(
961
- self: (
962
- ArrayVar[Tuple[int, OTHER_TUPLE]]
963
- | ArrayVar[Tuple[float, OTHER_TUPLE]]
964
- | ArrayVar[Tuple[int | float, OTHER_TUPLE]]
965
- ),
966
- i: Literal[0, -2],
967
- ) -> NumberVar: ...
958
+ @var_operation
959
+ def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
960
+ """Check if a string is less than another string.
968
961
 
969
- @overload
970
- def __getitem__(
971
- self: ArrayVar[Tuple[Any, bool]], i: Literal[1, -1]
972
- ) -> BooleanVar: ...
962
+ Args:
963
+ lhs: The left-hand side string.
964
+ rhs: The right-hand side string.
973
965
 
974
- @overload
975
- def __getitem__(
976
- self: (
977
- ArrayVar[Tuple[Any, int]]
978
- | ArrayVar[Tuple[Any, float]]
979
- | ArrayVar[Tuple[Any, int | float]]
980
- ),
981
- i: Literal[1, -1],
982
- ) -> NumberVar: ...
966
+ Returns:
967
+ The string less than operation.
968
+ """
969
+ return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool)
983
970
 
984
- @overload
985
- def __getitem__(
986
- self: ArrayVar[Tuple[str, Any]], i: Literal[0, -2]
987
- ) -> StringVar: ...
988
971
 
989
- @overload
990
- def __getitem__(
991
- self: ArrayVar[Tuple[Any, str]], i: Literal[1, -1]
992
- ) -> StringVar: ...
972
+ @var_operation
973
+ def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
974
+ """Check if a string is greater than another string.
993
975
 
994
- @overload
995
- def __getitem__(
996
- self: ArrayVar[Tuple[bool, Any]], i: Literal[0, -2]
997
- ) -> BooleanVar: ...
976
+ Args:
977
+ lhs: The left-hand side string.
978
+ rhs: The right-hand side string.
998
979
 
999
- @overload
1000
- def __getitem__(
1001
- self: ARRAY_VAR_OF_LIST_ELEMENT[bool], i: int | NumberVar
1002
- ) -> BooleanVar: ...
980
+ Returns:
981
+ The string greater than operation.
982
+ """
983
+ return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool)
1003
984
 
1004
- @overload
1005
- def __getitem__(
1006
- self: (
1007
- ARRAY_VAR_OF_LIST_ELEMENT[int]
1008
- | ARRAY_VAR_OF_LIST_ELEMENT[float]
1009
- | ARRAY_VAR_OF_LIST_ELEMENT[int | float]
1010
- ),
1011
- i: int | NumberVar,
1012
- ) -> NumberVar: ...
1013
985
 
1014
- @overload
1015
- def __getitem__(
1016
- self: ARRAY_VAR_OF_LIST_ELEMENT[str], i: int | NumberVar
1017
- ) -> StringVar: ...
986
+ @var_operation
987
+ def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
988
+ """Check if a string is less than or equal to another string.
1018
989
 
1019
- @overload
1020
- def __getitem__(
1021
- self: ARRAY_VAR_OF_LIST_ELEMENT[List[INNER_ARRAY_VAR]],
1022
- i: int | NumberVar,
1023
- ) -> ArrayVar[List[INNER_ARRAY_VAR]]: ...
990
+ Args:
991
+ lhs: The left-hand side string.
992
+ rhs: The right-hand side string.
1024
993
 
1025
- @overload
1026
- def __getitem__(
1027
- self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[KEY_TYPE, VALUE_TYPE]],
1028
- i: int | NumberVar,
1029
- ) -> ArrayVar[Tuple[KEY_TYPE, VALUE_TYPE]]: ...
994
+ Returns:
995
+ The string less than or equal operation.
996
+ """
997
+ return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool)
1030
998
 
1031
- @overload
1032
- def __getitem__(
1033
- self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[INNER_ARRAY_VAR, ...]],
1034
- i: int | NumberVar,
1035
- ) -> ArrayVar[Tuple[INNER_ARRAY_VAR, ...]]: ...
1036
999
 
1037
- @overload
1038
- def __getitem__(
1039
- self: ARRAY_VAR_OF_LIST_ELEMENT[Dict[KEY_TYPE, VALUE_TYPE]],
1040
- i: int | NumberVar,
1041
- ) -> ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]: ...
1000
+ @var_operation
1001
+ def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
1002
+ """Check if a string is greater than or equal to another string.
1042
1003
 
1043
- @overload
1044
- def __getitem__(
1045
- self: ARRAY_VAR_OF_LIST_ELEMENT[BASE_TYPE],
1046
- i: int | NumberVar,
1047
- ) -> ObjectVar[BASE_TYPE]: ...
1004
+ Args:
1005
+ lhs: The left-hand side string.
1006
+ rhs: The right-hand side string.
1048
1007
 
1049
- @overload
1050
- def __getitem__(
1051
- self: ARRAY_VAR_OF_LIST_ELEMENT[SQLA_TYPE],
1052
- i: int | NumberVar,
1053
- ) -> ObjectVar[SQLA_TYPE]: ...
1008
+ Returns:
1009
+ The string greater than or equal operation.
1010
+ """
1011
+ return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool)
1054
1012
 
1055
- @overload
1056
- def __getitem__(
1057
- self: ARRAY_VAR_OF_LIST_ELEMENT[DATACLASS_TYPE],
1058
- i: int | NumberVar,
1059
- ) -> ObjectVar[DATACLASS_TYPE]: ...
1060
1013
 
1061
- @overload
1062
- def __getitem__(self, i: int | NumberVar) -> Var: ...
1014
+ @var_operation
1015
+ def string_lower_operation(string: StringVar[Any]):
1016
+ """Convert a string to lowercase.
1063
1017
 
1064
- def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var:
1065
- """Get a slice of the array.
1018
+ Args:
1019
+ string: The string to convert.
1066
1020
 
1067
- Args:
1068
- i: The slice.
1021
+ Returns:
1022
+ The lowercase string.
1023
+ """
1024
+ return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str)
1069
1025
 
1070
- Returns:
1071
- The array slice operation.
1072
- """
1073
- if isinstance(i, slice):
1074
- return ArraySliceOperation.create(self, i)
1075
- if not isinstance(i, (int, NumberVar)) or (
1076
- isinstance(i, NumberVar) and i._is_strict_float()
1077
- ):
1078
- raise_unsupported_operand_types("[]", (type(self), type(i)))
1079
- return array_item_operation(self, i)
1080
1026
 
1081
- def length(self) -> NumberVar[int]:
1082
- """Get the length of the array.
1027
+ @var_operation
1028
+ def string_upper_operation(string: StringVar[Any]):
1029
+ """Convert a string to uppercase.
1083
1030
 
1084
- Returns:
1085
- The length of the array.
1086
- """
1087
- return array_length_operation(self)
1031
+ Args:
1032
+ string: The string to convert.
1088
1033
 
1089
- @overload
1090
- @classmethod
1091
- def range(cls, stop: int | NumberVar, /) -> ArrayVar[List[int]]: ...
1034
+ Returns:
1035
+ The uppercase string.
1036
+ """
1037
+ return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str)
1092
1038
 
1093
- @overload
1094
- @classmethod
1095
- def range(
1096
- cls,
1097
- start: int | NumberVar,
1098
- end: int | NumberVar,
1099
- step: int | NumberVar = 1,
1100
- /,
1101
- ) -> ArrayVar[List[int]]: ...
1102
1039
 
1103
- @overload
1104
- @classmethod
1105
- def range(
1106
- cls,
1107
- first_endpoint: int | NumberVar,
1108
- second_endpoint: int | NumberVar | None = None,
1109
- step: int | NumberVar | None = None,
1110
- ) -> ArrayVar[List[int]]: ...
1040
+ @var_operation
1041
+ def string_title_operation(string: StringVar[Any]):
1042
+ """Convert a string to title case.
1111
1043
 
1112
- @classmethod
1113
- def range(
1114
- cls,
1115
- first_endpoint: int | NumberVar,
1116
- second_endpoint: int | NumberVar | None = None,
1117
- step: int | NumberVar | None = None,
1118
- ) -> ArrayVar[List[int]]:
1119
- """Create a range of numbers.
1044
+ Args:
1045
+ string: The string to convert.
1120
1046
 
1121
- Args:
1122
- first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
1123
- second_endpoint: The end of the range.
1124
- step: The step of the range.
1047
+ Returns:
1048
+ The title case string.
1049
+ """
1050
+ return var_operation_return(
1051
+ js_expression=f"{string}.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')",
1052
+ var_type=str,
1053
+ )
1125
1054
 
1126
- Returns:
1127
- The range of numbers.
1128
- """
1129
- if any(
1130
- not isinstance(i, (int, NumberVar))
1131
- for i in (first_endpoint, second_endpoint, step)
1132
- if i is not None
1133
- ):
1134
- raise_unsupported_operand_types(
1135
- "range", (type(first_endpoint), type(second_endpoint), type(step))
1136
- )
1137
- if second_endpoint is None:
1138
- start = 0
1139
- end = first_endpoint
1140
- else:
1141
- start = first_endpoint
1142
- end = second_endpoint
1143
1055
 
1144
- return array_range_operation(start, end, step or 1)
1056
+ @var_operation
1057
+ def string_capitalize_operation(string: StringVar[Any]):
1058
+ """Capitalize a string.
1145
1059
 
1146
- @overload
1147
- def contains(self, other: Any) -> BooleanVar: ...
1060
+ Args:
1061
+ string: The string to capitalize.
1148
1062
 
1149
- @overload
1150
- def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ...
1063
+ Returns:
1064
+ The capitalized string.
1065
+ """
1066
+ return var_operation_return(
1067
+ js_expression=f"(((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())({string}))",
1068
+ var_type=str,
1069
+ )
1151
1070
 
1152
- def contains(self, other: Any, field: Any = None) -> BooleanVar:
1153
- """Check if the array contains an element.
1154
1071
 
1155
- Args:
1156
- other: The element to check for.
1157
- field: The field to check.
1072
+ @var_operation
1073
+ def string_strip_operation(string: StringVar[Any]):
1074
+ """Strip a string.
1158
1075
 
1159
- Returns:
1160
- The array contains operation.
1161
- """
1162
- if field is not None:
1163
- if not isinstance(field, (StringVar, str)):
1164
- raise_unsupported_operand_types("contains", (type(self), type(field)))
1165
- return array_contains_field_operation(self, other, field)
1166
- return array_contains_operation(self, other)
1076
+ Args:
1077
+ string: The string to strip.
1167
1078
 
1168
- def pluck(self, field: StringVar | str) -> ArrayVar:
1169
- """Pluck a field from the array.
1079
+ Returns:
1080
+ The stripped string.
1081
+ """
1082
+ return var_operation_return(js_expression=f"{string}.trim()", var_type=str)
1170
1083
 
1171
- Args:
1172
- field: The field to pluck from the array.
1173
1084
 
1174
- Returns:
1175
- The array pluck operation.
1176
- """
1177
- return array_pluck_operation(self, field)
1085
+ @var_operation
1086
+ def string_contains_field_operation(
1087
+ haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str
1088
+ ):
1089
+ """Check if a string contains another string.
1178
1090
 
1179
- @overload
1180
- def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]: ...
1091
+ Args:
1092
+ haystack: The haystack.
1093
+ needle: The needle.
1094
+ field: The field to check.
1181
1095
 
1182
- @overload
1183
- def __mul__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
1096
+ Returns:
1097
+ The string contains operation.
1098
+ """
1099
+ return var_operation_return(
1100
+ js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})",
1101
+ var_type=bool,
1102
+ )
1184
1103
 
1185
- def __mul__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
1186
- """Multiply the sequence by a number or integer.
1187
1104
 
1188
- Parameters:
1189
- other: The number or integer to multiply the sequence by.
1105
+ @var_operation
1106
+ def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str):
1107
+ """Check if a string contains another string.
1190
1108
 
1191
- Returns:
1192
- ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
1193
- """
1194
- if not isinstance(other, (NumberVar, int)) or (
1195
- isinstance(other, NumberVar) and other._is_strict_float()
1196
- ):
1197
- raise_unsupported_operand_types("*", (type(self), type(other)))
1109
+ Args:
1110
+ haystack: The haystack.
1111
+ needle: The needle.
1198
1112
 
1199
- return repeat_array_operation(self, other)
1113
+ Returns:
1114
+ The string contains operation.
1115
+ """
1116
+ return var_operation_return(
1117
+ js_expression=f"{haystack}.includes({needle})", var_type=bool
1118
+ )
1200
1119
 
1201
- __rmul__ = __mul__
1202
1120
 
1203
- @overload
1204
- def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
1121
+ @var_operation
1122
+ def string_starts_with_operation(
1123
+ full_string: StringVar[Any], prefix: StringVar[Any] | str
1124
+ ):
1125
+ """Check if a string starts with a prefix.
1205
1126
 
1206
- @overload
1207
- def __lt__(self, other: list | tuple) -> BooleanVar: ...
1127
+ Args:
1128
+ full_string: The full string.
1129
+ prefix: The prefix.
1208
1130
 
1209
- def __lt__(self, other: Any):
1210
- """Check if the array is less than another array.
1131
+ Returns:
1132
+ Whether the string starts with the prefix.
1133
+ """
1134
+ return var_operation_return(
1135
+ js_expression=f"{full_string}.startsWith({prefix})", var_type=bool
1136
+ )
1211
1137
 
1212
- Args:
1213
- other: The other array.
1214
1138
 
1215
- Returns:
1216
- The array less than operation.
1217
- """
1218
- if not isinstance(other, (ArrayVar, list, tuple)):
1219
- raise_unsupported_operand_types("<", (type(self), type(other)))
1139
+ @var_operation
1140
+ def string_ends_with_operation(
1141
+ full_string: StringVar[Any], suffix: StringVar[Any] | str
1142
+ ):
1143
+ """Check if a string ends with a suffix.
1220
1144
 
1221
- return array_lt_operation(self, other)
1145
+ Args:
1146
+ full_string: The full string.
1147
+ suffix: The suffix.
1222
1148
 
1223
- @overload
1224
- def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
1149
+ Returns:
1150
+ Whether the string ends with the suffix.
1151
+ """
1152
+ return var_operation_return(
1153
+ js_expression=f"{full_string}.endsWith({suffix})", var_type=bool
1154
+ )
1225
1155
 
1226
- @overload
1227
- def __gt__(self, other: list | tuple) -> BooleanVar: ...
1228
1156
 
1229
- def __gt__(self, other: Any):
1230
- """Check if the array is greater than another array.
1157
+ @var_operation
1158
+ def string_item_operation(string: StringVar[Any], index: NumberVar | int):
1159
+ """Get an item from a string.
1231
1160
 
1232
- Args:
1233
- other: The other array.
1161
+ Args:
1162
+ string: The string.
1163
+ index: The index of the item.
1234
1164
 
1235
- Returns:
1236
- The array greater than operation.
1237
- """
1238
- if not isinstance(other, (ArrayVar, list, tuple)):
1239
- raise_unsupported_operand_types(">", (type(self), type(other)))
1165
+ Returns:
1166
+ The item from the string.
1167
+ """
1168
+ return var_operation_return(js_expression=f"{string}.at({index})", var_type=str)
1240
1169
 
1241
- return array_gt_operation(self, other)
1242
1170
 
1243
- @overload
1244
- def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
1171
+ @var_operation
1172
+ def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
1173
+ """Join the elements of an array.
1245
1174
 
1246
- @overload
1247
- def __le__(self, other: list | tuple) -> BooleanVar: ...
1175
+ Args:
1176
+ array: The array.
1177
+ sep: The separator.
1248
1178
 
1249
- def __le__(self, other: Any):
1250
- """Check if the array is less than or equal to another array.
1179
+ Returns:
1180
+ The joined elements.
1181
+ """
1182
+ return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str)
1183
+
1184
+
1185
+ @var_operation
1186
+ def string_replace_operation(
1187
+ string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str
1188
+ ):
1189
+ """Replace a string with a value.
1190
+
1191
+ Args:
1192
+ string: The string.
1193
+ search_value: The string to search.
1194
+ new_value: The value to be replaced with.
1195
+
1196
+ Returns:
1197
+ The string replace operation.
1198
+ """
1199
+ return var_operation_return(
1200
+ js_expression=f"{string}.replaceAll({search_value}, {new_value})",
1201
+ var_type=str,
1202
+ )
1251
1203
 
1252
- Args:
1253
- other: The other array.
1254
1204
 
1255
- Returns:
1256
- The array less than or equal operation.
1257
- """
1258
- if not isinstance(other, (ArrayVar, list, tuple)):
1259
- raise_unsupported_operand_types("<=", (type(self), type(other)))
1205
+ # Compile regex for finding reflex var tags.
1206
+ _decode_var_pattern_re = (
1207
+ rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
1208
+ )
1209
+ _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
1260
1210
 
1261
- return array_le_operation(self, other)
1262
1211
 
1263
- @overload
1264
- def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
1212
+ @dataclasses.dataclass(
1213
+ eq=False,
1214
+ frozen=True,
1215
+ slots=True,
1216
+ )
1217
+ class LiteralStringVar(LiteralVar, StringVar[str]):
1218
+ """Base class for immutable literal string vars."""
1265
1219
 
1266
- @overload
1267
- def __ge__(self, other: list | tuple) -> BooleanVar: ...
1220
+ _var_value: str = dataclasses.field(default="")
1268
1221
 
1269
- def __ge__(self, other: Any):
1270
- """Check if the array is greater than or equal to another array.
1222
+ @classmethod
1223
+ def create(
1224
+ cls,
1225
+ value: str,
1226
+ _var_type: GenericType | None = None,
1227
+ _var_data: VarData | None = None,
1228
+ ) -> StringVar:
1229
+ """Create a var from a string value.
1271
1230
 
1272
1231
  Args:
1273
- other: The other array.
1232
+ value: The value to create the var from.
1233
+ _var_type: The type of the var.
1234
+ _var_data: Additional hooks and imports associated with the Var.
1274
1235
 
1275
1236
  Returns:
1276
- The array greater than or equal operation.
1237
+ The var.
1277
1238
  """
1278
- if not isinstance(other, (ArrayVar, list, tuple)):
1279
- raise_unsupported_operand_types(">=", (type(self), type(other)))
1239
+ # Determine var type in case the value is inherited from str.
1240
+ _var_type = _var_type or type(value) or str
1280
1241
 
1281
- return array_ge_operation(self, other)
1242
+ if REFLEX_VAR_OPENING_TAG in value:
1243
+ strings_and_vals: list[Var | str] = []
1244
+ offset = 0
1282
1245
 
1283
- def foreach(self, fn: Any):
1284
- """Apply a function to each element of the array.
1246
+ # Find all tags
1247
+ while m := _decode_var_pattern.search(value):
1248
+ start, end = m.span()
1285
1249
 
1286
- Args:
1287
- fn: The function to apply.
1250
+ strings_and_vals.append(value[:start])
1288
1251
 
1289
- Returns:
1290
- The array after applying the function.
1252
+ serialized_data = m.group(1)
1291
1253
 
1292
- Raises:
1293
- VarTypeError: If the function takes more than one argument.
1294
- """
1295
- from .function import ArgsFunctionOperation
1254
+ if serialized_data.isnumeric() or (
1255
+ serialized_data[0] == "-" and serialized_data[1:].isnumeric()
1256
+ ):
1257
+ # This is a global immutable var.
1258
+ var = _global_vars[int(serialized_data)]
1259
+ strings_and_vals.append(var)
1260
+ value = value[(end + len(var._js_expr)) :]
1296
1261
 
1297
- if not callable(fn):
1298
- raise_unsupported_operand_types("foreach", (type(self), type(fn)))
1299
- # get the number of arguments of the function
1300
- num_args = len(inspect.signature(fn).parameters)
1301
- if num_args > 1:
1302
- raise VarTypeError(
1303
- "The function passed to foreach should take at most one argument."
1304
- )
1262
+ offset += end - start
1305
1263
 
1306
- if num_args == 0:
1307
- return_value = fn()
1308
- function_var = ArgsFunctionOperation.create((), return_value)
1309
- else:
1310
- # generic number var
1311
- number_var = Var("").to(NumberVar, int)
1264
+ strings_and_vals.append(value)
1312
1265
 
1313
- first_arg_type = self[number_var]._var_type
1266
+ filtered_strings_and_vals = [
1267
+ s for s in strings_and_vals if isinstance(s, Var) or s
1268
+ ]
1269
+ if len(filtered_strings_and_vals) == 1:
1270
+ only_string = filtered_strings_and_vals[0]
1271
+ if isinstance(only_string, str):
1272
+ return LiteralVar.create(only_string).to(StringVar, _var_type)
1273
+ else:
1274
+ return only_string.to(StringVar, only_string._var_type)
1314
1275
 
1315
- arg_name = get_unique_variable_name()
1276
+ if len(
1277
+ literal_strings := [
1278
+ s
1279
+ for s in filtered_strings_and_vals
1280
+ if isinstance(s, (str, LiteralStringVar))
1281
+ ]
1282
+ ) == len(filtered_strings_and_vals):
1283
+ return LiteralStringVar.create(
1284
+ "".join(
1285
+ s._var_value if isinstance(s, LiteralStringVar) else s
1286
+ for s in literal_strings
1287
+ ),
1288
+ _var_type=_var_type,
1289
+ _var_data=VarData.merge(
1290
+ _var_data,
1291
+ *(
1292
+ s._get_all_var_data()
1293
+ for s in filtered_strings_and_vals
1294
+ if isinstance(s, Var)
1295
+ ),
1296
+ ),
1297
+ )
1316
1298
 
1317
- # get first argument type
1318
- first_arg = Var(
1319
- _js_expr=arg_name,
1320
- _var_type=first_arg_type,
1321
- ).guess_type()
1299
+ concat_result = ConcatVarOperation.create(
1300
+ *filtered_strings_and_vals,
1301
+ _var_data=_var_data,
1302
+ )
1322
1303
 
1323
- function_var = ArgsFunctionOperation.create(
1324
- (arg_name,),
1325
- Var.create(fn(first_arg)),
1304
+ return (
1305
+ concat_result
1306
+ if _var_type is str
1307
+ else concat_result.to(StringVar, _var_type)
1326
1308
  )
1327
1309
 
1328
- return map_array_operation(self, function_var)
1310
+ return LiteralStringVar(
1311
+ _js_expr=json.dumps(value),
1312
+ _var_type=_var_type,
1313
+ _var_data=_var_data,
1314
+ _var_value=value,
1315
+ )
1316
+
1317
+ def __hash__(self) -> int:
1318
+ """Get the hash of the var.
1329
1319
 
1320
+ Returns:
1321
+ The hash of the var.
1322
+ """
1323
+ return hash((type(self).__name__, self._var_value))
1330
1324
 
1331
- LIST_ELEMENT = TypeVar("LIST_ELEMENT")
1325
+ def json(self) -> str:
1326
+ """Get the JSON representation of the var.
1332
1327
 
1333
- ARRAY_VAR_OF_LIST_ELEMENT = ArrayVar[Sequence[LIST_ELEMENT]]
1328
+ Returns:
1329
+ The JSON representation of the var.
1330
+ """
1331
+ return json.dumps(self._var_value)
1334
1332
 
1335
1333
 
1336
1334
  @dataclasses.dataclass(
@@ -1338,10 +1336,10 @@ ARRAY_VAR_OF_LIST_ELEMENT = ArrayVar[Sequence[LIST_ELEMENT]]
1338
1336
  frozen=True,
1339
1337
  slots=True,
1340
1338
  )
1341
- class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
1342
- """Base class for immutable literal array vars."""
1339
+ class ConcatVarOperation(CachedVarOperation, StringVar[str]):
1340
+ """Representing a concatenation of literal string vars."""
1343
1341
 
1344
- _var_value: Sequence[Union[Var, Any]] = dataclasses.field(default=())
1342
+ _var_value: tuple[Var, ...] = dataclasses.field(default_factory=tuple)
1345
1343
 
1346
1344
  @cached_property_no_lock
1347
1345
  def _cached_var_name(self) -> str:
@@ -1350,79 +1348,65 @@ class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
1350
1348
  Returns:
1351
1349
  The name of the var.
1352
1350
  """
1353
- return (
1354
- "["
1355
- + ", ".join(
1356
- [str(LiteralVar.create(element)) for element in self._var_value]
1357
- )
1358
- + "]"
1359
- )
1351
+ list_of_strs: list[str | Var] = []
1352
+ last_string = ""
1353
+ for var in self._var_value:
1354
+ if isinstance(var, LiteralStringVar):
1355
+ last_string += var._var_value
1356
+ else:
1357
+ if last_string:
1358
+ list_of_strs.append(last_string)
1359
+ last_string = ""
1360
+ list_of_strs.append(var)
1361
+
1362
+ if last_string:
1363
+ list_of_strs.append(last_string)
1364
+
1365
+ list_of_strs_filtered = [
1366
+ str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s
1367
+ ]
1368
+
1369
+ if len(list_of_strs_filtered) == 1:
1370
+ return list_of_strs_filtered[0]
1371
+
1372
+ return "(" + "+".join(list_of_strs_filtered) + ")"
1360
1373
 
1361
1374
  @cached_property_no_lock
1362
1375
  def _cached_get_all_var_data(self) -> VarData | None:
1363
- """Get all the VarData associated with the Var.
1376
+ """Get all the VarData asVarDatae Var.
1364
1377
 
1365
1378
  Returns:
1366
1379
  The VarData associated with the Var.
1367
1380
  """
1368
1381
  return VarData.merge(
1369
1382
  *[
1370
- LiteralVar.create(element)._get_all_var_data()
1371
- for element in self._var_value
1383
+ var._get_all_var_data()
1384
+ for var in self._var_value
1385
+ if isinstance(var, Var)
1372
1386
  ],
1373
1387
  self._var_data,
1374
1388
  )
1375
1389
 
1376
- def __hash__(self) -> int:
1377
- """Get the hash of the var.
1378
-
1379
- Returns:
1380
- The hash of the var.
1381
- """
1382
- return hash((self.__class__.__name__, self._js_expr))
1383
-
1384
- def json(self) -> str:
1385
- """Get the JSON representation of the var.
1386
-
1387
- Returns:
1388
- The JSON representation of the var.
1389
-
1390
- Raises:
1391
- TypeError: If the array elements are not of type LiteralVar.
1392
- """
1393
- elements = []
1394
- for element in self._var_value:
1395
- element_var = LiteralVar.create(element)
1396
- if not isinstance(element_var, LiteralVar):
1397
- raise TypeError(
1398
- f"Array elements must be of type LiteralVar, not {type(element_var)}"
1399
- )
1400
- elements.append(element_var.json())
1401
-
1402
- return "[" + ", ".join(elements) + "]"
1403
-
1404
1390
  @classmethod
1405
1391
  def create(
1406
1392
  cls,
1407
- value: OTHER_ARRAY_VAR_TYPE,
1408
- _var_type: Type[OTHER_ARRAY_VAR_TYPE] | None = None,
1393
+ *value: Var | str,
1409
1394
  _var_data: VarData | None = None,
1410
- ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]:
1395
+ ) -> ConcatVarOperation:
1411
1396
  """Create a var from a string value.
1412
1397
 
1413
1398
  Args:
1414
- value: The value to create the var from.
1415
- _var_type: The type of the var.
1399
+ *value: The values to concatenate.
1416
1400
  _var_data: Additional hooks and imports associated with the Var.
1417
1401
 
1418
1402
  Returns:
1419
1403
  The var.
1420
1404
  """
1421
- return LiteralArrayVar(
1405
+ return cls(
1422
1406
  _js_expr="",
1423
- _var_type=figure_out_type(value) if _var_type is None else _var_type,
1407
+ _var_type=str,
1424
1408
  _var_data=_var_data,
1425
- _var_value=value,
1409
+ _var_value=tuple(map(LiteralVar.create, value)),
1426
1410
  )
1427
1411
 
1428
1412
 
@@ -1438,7 +1422,7 @@ def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""):
1438
1422
  The split string.
1439
1423
  """
1440
1424
  return var_operation_return(
1441
- js_expression=f"{string}.split({sep})", var_type=List[str]
1425
+ js_expression=f"{string}.split({sep})", var_type=list[str]
1442
1426
  )
1443
1427
 
1444
1428
 
@@ -1638,11 +1622,50 @@ def is_tuple_type(t: GenericType) -> bool:
1638
1622
  Returns:
1639
1623
  Whether the type is a tuple type.
1640
1624
  """
1641
- if inspect.isclass(t):
1642
- return issubclass(t, tuple)
1643
1625
  return get_origin(t) is tuple
1644
1626
 
1645
1627
 
1628
+ def _determine_value_of_array_index(
1629
+ var_type: GenericType, index: int | float | None = None
1630
+ ):
1631
+ """Determine the value of an array index.
1632
+
1633
+ Args:
1634
+ var_type: The type of the array.
1635
+ index: The index of the array.
1636
+
1637
+ Returns:
1638
+ The value of the array index.
1639
+ """
1640
+ origin_var_type = get_origin(var_type) or var_type
1641
+ if origin_var_type in types.UnionTypes:
1642
+ return unionize(
1643
+ *[
1644
+ _determine_value_of_array_index(t, index)
1645
+ for t in get_args(var_type)
1646
+ if t is not type(None)
1647
+ ]
1648
+ )
1649
+ if origin_var_type in [
1650
+ Sequence,
1651
+ Iterable,
1652
+ list,
1653
+ set,
1654
+ collections.abc.Sequence,
1655
+ collections.abc.Iterable,
1656
+ ]:
1657
+ args = get_args(var_type)
1658
+ return args[0] if args else Any
1659
+ if origin_var_type is tuple:
1660
+ args = get_args(var_type)
1661
+ return (
1662
+ args[int(index) % len(args)]
1663
+ if args and index is not None
1664
+ else (unionize(*args) if args else Any)
1665
+ )
1666
+ return Any
1667
+
1668
+
1646
1669
  @var_operation
1647
1670
  def array_item_operation(array: ArrayVar, index: NumberVar | int):
1648
1671
  """Get an item from an array.
@@ -1654,12 +1677,14 @@ def array_item_operation(array: ArrayVar, index: NumberVar | int):
1654
1677
  Returns:
1655
1678
  The item from the array.
1656
1679
  """
1657
- args = typing.get_args(array._var_type)
1658
- if args and isinstance(index, LiteralNumberVar) and is_tuple_type(array._var_type):
1659
- index_value = int(index._var_value)
1660
- element_type = args[index_value % len(args)]
1661
- else:
1662
- element_type = unionize(*args)
1680
+ element_type = _determine_value_of_array_index(
1681
+ array._var_type,
1682
+ (
1683
+ index
1684
+ if isinstance(index, int)
1685
+ else (index._var_value if isinstance(index, LiteralNumberVar) else None)
1686
+ ),
1687
+ )
1663
1688
 
1664
1689
  return var_operation_return(
1665
1690
  js_expression=f"{array!s}.at({index!s})",
@@ -1683,7 +1708,7 @@ def array_range_operation(
1683
1708
  """
1684
1709
  return var_operation_return(
1685
1710
  js_expression=f"Array.from({{ length: Math.ceil(({stop!s} - {start!s}) / {step!s}) }}, (_, i) => {start!s} + i * {step!s})",
1686
- var_type=List[int],
1711
+ var_type=list[int],
1687
1712
  )
1688
1713
 
1689
1714
 
@@ -1749,7 +1774,7 @@ def repeat_array_operation(
1749
1774
  def map_array_operation(
1750
1775
  array: ArrayVar[ARRAY_VAR_TYPE],
1751
1776
  function: FunctionVar,
1752
- ) -> CustomVarOperationReturn[List[Any]]:
1777
+ ) -> CustomVarOperationReturn[list[Any]]:
1753
1778
  """Map a function over an array.
1754
1779
 
1755
1780
  Args:
@@ -1760,7 +1785,7 @@ def map_array_operation(
1760
1785
  The mapped array.
1761
1786
  """
1762
1787
  return var_operation_return(
1763
- js_expression=f"{array}.map({function})", var_type=List[Any]
1788
+ js_expression=f"{array}.map({function})", var_type=list[Any]
1764
1789
  )
1765
1790
 
1766
1791