sequence.py 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958
  1. """Collection of string classes and utilities."""
  2. from __future__ import annotations
  3. import collections.abc
  4. import dataclasses
  5. import decimal
  6. import inspect
  7. import json
  8. import re
  9. from collections.abc import Iterable, Mapping, Sequence
  10. from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_args, overload
  11. from typing_extensions import TypeVar as TypingExtensionsTypeVar
  12. from reflex import constants
  13. from reflex.constants.base import REFLEX_VAR_OPENING_TAG
  14. from reflex.constants.colors import Color
  15. from reflex.utils import types
  16. from reflex.utils.exceptions import VarTypeError
  17. from reflex.utils.types import GenericType, get_origin
  18. from .base import (
  19. CachedVarOperation,
  20. CustomVarOperationReturn,
  21. LiteralVar,
  22. Var,
  23. VarData,
  24. _global_vars,
  25. cached_property_no_lock,
  26. figure_out_type,
  27. get_python_literal,
  28. get_unique_variable_name,
  29. unionize,
  30. var_operation,
  31. var_operation_return,
  32. )
  33. from .number import (
  34. BooleanVar,
  35. LiteralNumberVar,
  36. NumberVar,
  37. raise_unsupported_operand_types,
  38. ternary_operation,
  39. )
  40. if TYPE_CHECKING:
  41. from .base import BASE_TYPE, DATACLASS_TYPE, SQLA_TYPE
  42. from .function import FunctionVar
  43. from .object import ObjectVar
  44. ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Sequence, covariant=True)
  45. OTHER_ARRAY_VAR_TYPE = TypeVar("OTHER_ARRAY_VAR_TYPE", bound=Sequence, covariant=True)
  46. MAPPING_VAR_TYPE = TypeVar("MAPPING_VAR_TYPE", bound=Mapping, covariant=True)
  47. OTHER_TUPLE = TypeVar("OTHER_TUPLE")
  48. INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR")
  49. KEY_TYPE = TypeVar("KEY_TYPE")
  50. VALUE_TYPE = TypeVar("VALUE_TYPE")
  51. class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(Sequence, set)):
  52. """Base class for immutable array vars."""
  53. def join(self, sep: StringVar | str = "") -> StringVar:
  54. """Join the elements of the array.
  55. Args:
  56. sep: The separator between elements.
  57. Returns:
  58. The joined elements.
  59. """
  60. if not isinstance(sep, (StringVar, str)):
  61. raise_unsupported_operand_types("join", (type(self), type(sep)))
  62. if (
  63. isinstance(self, LiteralArrayVar)
  64. and (
  65. len(
  66. args := [
  67. x
  68. for x in self._var_value
  69. if isinstance(x, (LiteralStringVar, str))
  70. ]
  71. )
  72. == len(self._var_value)
  73. )
  74. and isinstance(sep, (LiteralStringVar, str))
  75. ):
  76. sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep
  77. return LiteralStringVar.create(
  78. sep_str.join(
  79. i._var_value if isinstance(i, LiteralStringVar) else i for i in args
  80. )
  81. )
  82. return array_join_operation(self, sep)
  83. def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]:
  84. """Reverse the array.
  85. Returns:
  86. The reversed array.
  87. """
  88. return array_reverse_operation(self)
  89. def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]:
  90. """Concatenate two arrays.
  91. Parameters:
  92. other: The other array to concatenate.
  93. Returns:
  94. ArrayConcatOperation: The concatenation of the two arrays.
  95. """
  96. if not isinstance(other, ArrayVar):
  97. raise_unsupported_operand_types("+", (type(self), type(other)))
  98. return array_concat_operation(self, other)
  99. @overload
  100. def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ...
  101. @overload
  102. def __getitem__(
  103. self: (
  104. ArrayVar[tuple[int, OTHER_TUPLE]]
  105. | ArrayVar[tuple[float, OTHER_TUPLE]]
  106. | ArrayVar[tuple[int | float, OTHER_TUPLE]]
  107. ),
  108. i: Literal[0, -2],
  109. ) -> NumberVar: ...
  110. @overload
  111. def __getitem__(
  112. self: ArrayVar[tuple[Any, bool]], i: Literal[1, -1]
  113. ) -> BooleanVar: ...
  114. @overload
  115. def __getitem__(
  116. self: (
  117. ArrayVar[tuple[Any, int]]
  118. | ArrayVar[tuple[Any, float]]
  119. | ArrayVar[tuple[Any, int | float]]
  120. ),
  121. i: Literal[1, -1],
  122. ) -> NumberVar: ...
  123. @overload
  124. def __getitem__( # pyright: ignore [reportOverlappingOverload]
  125. self: ArrayVar[tuple[str, Any]], i: Literal[0, -2]
  126. ) -> StringVar: ...
  127. @overload
  128. def __getitem__(
  129. self: ArrayVar[tuple[Any, str]], i: Literal[1, -1]
  130. ) -> StringVar: ...
  131. @overload
  132. def __getitem__(
  133. self: ArrayVar[tuple[bool, Any]], i: Literal[0, -2]
  134. ) -> BooleanVar: ...
  135. @overload
  136. def __getitem__(
  137. self: ArrayVar[Sequence[bool]], i: int | NumberVar
  138. ) -> BooleanVar: ...
  139. @overload
  140. def __getitem__(
  141. self: (
  142. ArrayVar[Sequence[int]]
  143. | ArrayVar[Sequence[float]]
  144. | ArrayVar[Sequence[int | float]]
  145. ),
  146. i: int | NumberVar,
  147. ) -> NumberVar: ...
  148. @overload
  149. def __getitem__(self: ArrayVar[Sequence[str]], i: int | NumberVar) -> StringVar: ...
  150. @overload
  151. def __getitem__(
  152. self: ArrayVar[Sequence[OTHER_ARRAY_VAR_TYPE]],
  153. i: int | NumberVar,
  154. ) -> ArrayVar[OTHER_ARRAY_VAR_TYPE]: ...
  155. @overload
  156. def __getitem__(
  157. self: ArrayVar[Sequence[MAPPING_VAR_TYPE]],
  158. i: int | NumberVar,
  159. ) -> ObjectVar[MAPPING_VAR_TYPE]: ...
  160. @overload
  161. def __getitem__(
  162. self: ArrayVar[Sequence[BASE_TYPE]],
  163. i: int | NumberVar,
  164. ) -> ObjectVar[BASE_TYPE]: ...
  165. @overload
  166. def __getitem__(
  167. self: ArrayVar[Sequence[SQLA_TYPE]],
  168. i: int | NumberVar,
  169. ) -> ObjectVar[SQLA_TYPE]: ...
  170. @overload
  171. def __getitem__(
  172. self: ArrayVar[Sequence[DATACLASS_TYPE]],
  173. i: int | NumberVar,
  174. ) -> ObjectVar[DATACLASS_TYPE]: ...
  175. @overload
  176. def __getitem__(self, i: int | NumberVar) -> Var: ...
  177. def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var:
  178. """Get a slice of the array.
  179. Args:
  180. i: The slice.
  181. Returns:
  182. The array slice operation.
  183. """
  184. if isinstance(i, slice):
  185. return ArraySliceOperation.create(self, i)
  186. if not isinstance(i, (int, NumberVar)) or (
  187. isinstance(i, NumberVar) and i._is_strict_float()
  188. ):
  189. raise_unsupported_operand_types("[]", (type(self), type(i)))
  190. return array_item_operation(self, i)
  191. def length(self) -> NumberVar[int]:
  192. """Get the length of the array.
  193. Returns:
  194. The length of the array.
  195. """
  196. return array_length_operation(self)
  197. @overload
  198. @classmethod
  199. def range(cls, stop: int | NumberVar, /) -> ArrayVar[list[int]]: ...
  200. @overload
  201. @classmethod
  202. def range(
  203. cls,
  204. start: int | NumberVar,
  205. end: int | NumberVar,
  206. step: int | NumberVar = 1,
  207. /,
  208. ) -> ArrayVar[list[int]]: ...
  209. @overload
  210. @classmethod
  211. def range(
  212. cls,
  213. first_endpoint: int | NumberVar,
  214. second_endpoint: int | NumberVar | None = None,
  215. step: int | NumberVar | None = None,
  216. ) -> ArrayVar[list[int]]: ...
  217. @classmethod
  218. def range(
  219. cls,
  220. first_endpoint: int | NumberVar,
  221. second_endpoint: int | NumberVar | None = None,
  222. step: int | NumberVar | None = None,
  223. ) -> ArrayVar[list[int]]:
  224. """Create a range of numbers.
  225. Args:
  226. first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
  227. second_endpoint: The end of the range.
  228. step: The step of the range.
  229. Returns:
  230. The range of numbers.
  231. """
  232. if any(
  233. not isinstance(i, (int, NumberVar))
  234. for i in (first_endpoint, second_endpoint, step)
  235. if i is not None
  236. ):
  237. raise_unsupported_operand_types(
  238. "range", (type(first_endpoint), type(second_endpoint), type(step))
  239. )
  240. if second_endpoint is None:
  241. start = 0
  242. end = first_endpoint
  243. else:
  244. start = first_endpoint
  245. end = second_endpoint
  246. return array_range_operation(start, end, step or 1)
  247. @overload
  248. def contains(self, other: Any) -> BooleanVar: ...
  249. @overload
  250. def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ...
  251. def contains(self, other: Any, field: Any = None) -> BooleanVar:
  252. """Check if the array contains an element.
  253. Args:
  254. other: The element to check for.
  255. field: The field to check.
  256. Returns:
  257. The array contains operation.
  258. """
  259. if field is not None:
  260. if not isinstance(field, (StringVar, str)):
  261. raise_unsupported_operand_types("contains", (type(self), type(field)))
  262. return array_contains_field_operation(self, other, field)
  263. return array_contains_operation(self, other)
  264. def pluck(self, field: StringVar | str) -> ArrayVar:
  265. """Pluck a field from the array.
  266. Args:
  267. field: The field to pluck from the array.
  268. Returns:
  269. The array pluck operation.
  270. """
  271. return array_pluck_operation(self, field)
  272. def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]:
  273. """Multiply the sequence by a number or integer.
  274. Parameters:
  275. other: The number or integer to multiply the sequence by.
  276. Returns:
  277. ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
  278. """
  279. if not isinstance(other, (NumberVar, int)) or (
  280. isinstance(other, NumberVar) and other._is_strict_float()
  281. ):
  282. raise_unsupported_operand_types("*", (type(self), type(other)))
  283. return repeat_array_operation(self, other)
  284. __rmul__ = __mul__
  285. @overload
  286. def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  287. @overload
  288. def __lt__(self, other: list | tuple) -> BooleanVar: ...
  289. def __lt__(self, other: Any):
  290. """Check if the array is less than another array.
  291. Args:
  292. other: The other array.
  293. Returns:
  294. The array less than operation.
  295. """
  296. if not isinstance(other, (ArrayVar, list, tuple)):
  297. raise_unsupported_operand_types("<", (type(self), type(other)))
  298. return array_lt_operation(self, other)
  299. @overload
  300. def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  301. @overload
  302. def __gt__(self, other: list | tuple) -> BooleanVar: ...
  303. def __gt__(self, other: Any):
  304. """Check if the array is greater than another array.
  305. Args:
  306. other: The other array.
  307. Returns:
  308. The array greater than operation.
  309. """
  310. if not isinstance(other, (ArrayVar, list, tuple)):
  311. raise_unsupported_operand_types(">", (type(self), type(other)))
  312. return array_gt_operation(self, other)
  313. @overload
  314. def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  315. @overload
  316. def __le__(self, other: list | tuple) -> BooleanVar: ...
  317. def __le__(self, other: Any):
  318. """Check if the array is less than or equal to another array.
  319. Args:
  320. other: The other array.
  321. Returns:
  322. The array less than or equal operation.
  323. """
  324. if not isinstance(other, (ArrayVar, list, tuple)):
  325. raise_unsupported_operand_types("<=", (type(self), type(other)))
  326. return array_le_operation(self, other)
  327. @overload
  328. def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  329. @overload
  330. def __ge__(self, other: list | tuple) -> BooleanVar: ...
  331. def __ge__(self, other: Any):
  332. """Check if the array is greater than or equal to another array.
  333. Args:
  334. other: The other array.
  335. Returns:
  336. The array greater than or equal operation.
  337. """
  338. if not isinstance(other, (ArrayVar, list, tuple)):
  339. raise_unsupported_operand_types(">=", (type(self), type(other)))
  340. return array_ge_operation(self, other)
  341. def foreach(self, fn: Any):
  342. """Apply a function to each element of the array.
  343. Args:
  344. fn: The function to apply.
  345. Returns:
  346. The array after applying the function.
  347. Raises:
  348. VarTypeError: If the function takes more than one argument.
  349. """
  350. from .function import ArgsFunctionOperation
  351. if not callable(fn):
  352. raise_unsupported_operand_types("foreach", (type(self), type(fn)))
  353. # get the number of arguments of the function
  354. num_args = len(inspect.signature(fn).parameters)
  355. if num_args > 1:
  356. raise VarTypeError(
  357. "The function passed to foreach should take at most one argument."
  358. )
  359. if num_args == 0:
  360. return_value = fn()
  361. function_var = ArgsFunctionOperation.create((), return_value)
  362. else:
  363. # generic number var
  364. number_var = Var("").to(NumberVar, int)
  365. first_arg_type = self[number_var]._var_type
  366. arg_name = get_unique_variable_name()
  367. # get first argument type
  368. first_arg = Var(
  369. _js_expr=arg_name,
  370. _var_type=first_arg_type,
  371. ).guess_type()
  372. function_var = ArgsFunctionOperation.create(
  373. (arg_name,),
  374. Var.create(fn(first_arg)),
  375. )
  376. return map_array_operation(self, function_var)
  377. @dataclasses.dataclass(
  378. eq=False,
  379. frozen=True,
  380. slots=True,
  381. )
  382. class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
  383. """Base class for immutable literal array vars."""
  384. _var_value: Sequence[Var | Any] = dataclasses.field(default=())
  385. @cached_property_no_lock
  386. def _cached_var_name(self) -> str:
  387. """The name of the var.
  388. Returns:
  389. The name of the var.
  390. """
  391. return (
  392. "["
  393. + ", ".join(
  394. [str(LiteralVar.create(element)) for element in self._var_value]
  395. )
  396. + "]"
  397. )
  398. @cached_property_no_lock
  399. def _cached_get_all_var_data(self) -> VarData | None:
  400. """Get all the VarData associated with the Var.
  401. Returns:
  402. The VarData associated with the Var.
  403. """
  404. return VarData.merge(
  405. *[
  406. LiteralVar.create(element)._get_all_var_data()
  407. for element in self._var_value
  408. ],
  409. self._var_data,
  410. )
  411. def __hash__(self) -> int:
  412. """Get the hash of the var.
  413. Returns:
  414. The hash of the var.
  415. """
  416. return hash((self.__class__.__name__, self._js_expr))
  417. def json(self) -> str:
  418. """Get the JSON representation of the var.
  419. Returns:
  420. The JSON representation of the var.
  421. Raises:
  422. TypeError: If the array elements are not of type LiteralVar.
  423. """
  424. elements = []
  425. for element in self._var_value:
  426. element_var = LiteralVar.create(element)
  427. if not isinstance(element_var, LiteralVar):
  428. raise TypeError(
  429. f"Array elements must be of type LiteralVar, not {type(element_var)}"
  430. )
  431. elements.append(element_var.json())
  432. return "[" + ", ".join(elements) + "]"
  433. @classmethod
  434. def create(
  435. cls,
  436. value: OTHER_ARRAY_VAR_TYPE,
  437. _var_type: type[OTHER_ARRAY_VAR_TYPE] | None = None,
  438. _var_data: VarData | None = None,
  439. ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]:
  440. """Create a var from a string value.
  441. Args:
  442. value: The value to create the var from.
  443. _var_type: The type of the var.
  444. _var_data: Additional hooks and imports associated with the Var.
  445. Returns:
  446. The var.
  447. """
  448. return LiteralArrayVar(
  449. _js_expr="",
  450. _var_type=figure_out_type(value) if _var_type is None else _var_type,
  451. _var_data=_var_data,
  452. _var_value=value,
  453. )
  454. STRING_TYPE = TypingExtensionsTypeVar("STRING_TYPE", default=str)
  455. class StringVar(Var[STRING_TYPE], python_types=str):
  456. """Base class for immutable string vars."""
  457. def __add__(self, other: StringVar | str) -> ConcatVarOperation:
  458. """Concatenate two strings.
  459. Args:
  460. other: The other string.
  461. Returns:
  462. The string concatenation operation.
  463. """
  464. if not isinstance(other, (StringVar, str)):
  465. raise_unsupported_operand_types("+", (type(self), type(other)))
  466. return ConcatVarOperation.create(self, other)
  467. def __radd__(self, other: StringVar | str) -> ConcatVarOperation:
  468. """Concatenate two strings.
  469. Args:
  470. other: The other string.
  471. Returns:
  472. The string concatenation operation.
  473. """
  474. if not isinstance(other, (StringVar, str)):
  475. raise_unsupported_operand_types("+", (type(other), type(self)))
  476. return ConcatVarOperation.create(other, self)
  477. def __mul__(self, other: NumberVar | int) -> StringVar:
  478. """Multiply the sequence by a number or an integer.
  479. Args:
  480. other: The number or integer to multiply the sequence by.
  481. Returns:
  482. StringVar: The resulting sequence after multiplication.
  483. """
  484. if not isinstance(other, (NumberVar, int)):
  485. raise_unsupported_operand_types("*", (type(self), type(other)))
  486. return (self.split() * other).join()
  487. def __rmul__(self, other: NumberVar | int) -> StringVar:
  488. """Multiply the sequence by a number or an integer.
  489. Args:
  490. other: The number or integer to multiply the sequence by.
  491. Returns:
  492. StringVar: The resulting sequence after multiplication.
  493. """
  494. if not isinstance(other, (NumberVar, int)):
  495. raise_unsupported_operand_types("*", (type(other), type(self)))
  496. return (self.split() * other).join()
  497. @overload
  498. def __getitem__(self, i: slice) -> StringVar: ...
  499. @overload
  500. def __getitem__(self, i: int | NumberVar) -> StringVar: ...
  501. def __getitem__(self, i: Any) -> StringVar:
  502. """Get a slice of the string.
  503. Args:
  504. i: The slice.
  505. Returns:
  506. The string slice operation.
  507. """
  508. if isinstance(i, slice):
  509. return self.split()[i].join()
  510. if not isinstance(i, (int, NumberVar)) or (
  511. isinstance(i, NumberVar) and i._is_strict_float()
  512. ):
  513. raise_unsupported_operand_types("[]", (type(self), type(i)))
  514. return string_item_operation(self, i)
  515. def length(self) -> NumberVar:
  516. """Get the length of the string.
  517. Returns:
  518. The string length operation.
  519. """
  520. return self.split().length()
  521. def lower(self) -> StringVar:
  522. """Convert the string to lowercase.
  523. Returns:
  524. The string lower operation.
  525. """
  526. return string_lower_operation(self)
  527. def upper(self) -> StringVar:
  528. """Convert the string to uppercase.
  529. Returns:
  530. The string upper operation.
  531. """
  532. return string_upper_operation(self)
  533. def title(self) -> StringVar:
  534. """Convert the string to title case.
  535. Returns:
  536. The string title operation.
  537. """
  538. return string_title_operation(self)
  539. def capitalize(self) -> StringVar:
  540. """Capitalize the string.
  541. Returns:
  542. The string capitalize operation.
  543. """
  544. return string_capitalize_operation(self)
  545. def strip(self) -> StringVar:
  546. """Strip the string.
  547. Returns:
  548. The string strip operation.
  549. """
  550. return string_strip_operation(self)
  551. def reversed(self) -> StringVar:
  552. """Reverse the string.
  553. Returns:
  554. The string reverse operation.
  555. """
  556. return self.split().reverse().join()
  557. def contains(
  558. self, other: StringVar | str, field: StringVar | str | None = None
  559. ) -> BooleanVar:
  560. """Check if the string contains another string.
  561. Args:
  562. other: The other string.
  563. field: The field to check.
  564. Returns:
  565. The string contains operation.
  566. """
  567. if not isinstance(other, (StringVar, str)):
  568. raise_unsupported_operand_types("contains", (type(self), type(other)))
  569. if field is not None:
  570. if not isinstance(field, (StringVar, str)):
  571. raise_unsupported_operand_types("contains", (type(self), type(field)))
  572. return string_contains_field_operation(self, other, field)
  573. return string_contains_operation(self, other)
  574. def split(self, separator: StringVar | str = "") -> ArrayVar[list[str]]:
  575. """Split the string.
  576. Args:
  577. separator: The separator.
  578. Returns:
  579. The string split operation.
  580. """
  581. if not isinstance(separator, (StringVar, str)):
  582. raise_unsupported_operand_types("split", (type(self), type(separator)))
  583. return string_split_operation(self, separator)
  584. def startswith(self, prefix: StringVar | str) -> BooleanVar:
  585. """Check if the string starts with a prefix.
  586. Args:
  587. prefix: The prefix.
  588. Returns:
  589. The string starts with operation.
  590. """
  591. if not isinstance(prefix, (StringVar, str)):
  592. raise_unsupported_operand_types("startswith", (type(self), type(prefix)))
  593. return string_starts_with_operation(self, prefix)
  594. def endswith(self, suffix: StringVar | str) -> BooleanVar:
  595. """Check if the string ends with a suffix.
  596. Args:
  597. suffix: The suffix.
  598. Returns:
  599. The string ends with operation.
  600. """
  601. if not isinstance(suffix, (StringVar, str)):
  602. raise_unsupported_operand_types("endswith", (type(self), type(suffix)))
  603. return string_ends_with_operation(self, suffix)
  604. def __lt__(self, other: StringVar | str) -> BooleanVar:
  605. """Check if the string is less than another string.
  606. Args:
  607. other: The other string.
  608. Returns:
  609. The string less than operation.
  610. """
  611. if not isinstance(other, (StringVar, str)):
  612. raise_unsupported_operand_types("<", (type(self), type(other)))
  613. return string_lt_operation(self, other)
  614. def __gt__(self, other: StringVar | str) -> BooleanVar:
  615. """Check if the string is greater than another string.
  616. Args:
  617. other: The other string.
  618. Returns:
  619. The string greater than operation.
  620. """
  621. if not isinstance(other, (StringVar, str)):
  622. raise_unsupported_operand_types(">", (type(self), type(other)))
  623. return string_gt_operation(self, other)
  624. def __le__(self, other: StringVar | str) -> BooleanVar:
  625. """Check if the string is less than or equal to another string.
  626. Args:
  627. other: The other string.
  628. Returns:
  629. The string less than or equal operation.
  630. """
  631. if not isinstance(other, (StringVar, str)):
  632. raise_unsupported_operand_types("<=", (type(self), type(other)))
  633. return string_le_operation(self, other)
  634. def __ge__(self, other: StringVar | str) -> BooleanVar:
  635. """Check if the string is greater than or equal to another string.
  636. Args:
  637. other: The other string.
  638. Returns:
  639. The string greater than or equal operation.
  640. """
  641. if not isinstance(other, (StringVar, str)):
  642. raise_unsupported_operand_types(">=", (type(self), type(other)))
  643. return string_ge_operation(self, other)
  644. @overload
  645. def replace( # pyright: ignore [reportOverlappingOverload]
  646. self, search_value: StringVar | str, new_value: StringVar | str
  647. ) -> StringVar: ...
  648. @overload
  649. def replace(
  650. self, search_value: Any, new_value: Any
  651. ) -> CustomVarOperationReturn[StringVar]: ...
  652. def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload]
  653. """Replace a string with a value.
  654. Args:
  655. search_value: The string to search.
  656. new_value: The value to be replaced with.
  657. Returns:
  658. The string replace operation.
  659. """
  660. if not isinstance(search_value, (StringVar, str)):
  661. raise_unsupported_operand_types("replace", (type(self), type(search_value)))
  662. if not isinstance(new_value, (StringVar, str)):
  663. raise_unsupported_operand_types("replace", (type(self), type(new_value)))
  664. return string_replace_operation(self, search_value, new_value)
  665. @var_operation
  666. def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
  667. """Check if a string is less than another string.
  668. Args:
  669. lhs: The left-hand side string.
  670. rhs: The right-hand side string.
  671. Returns:
  672. The string less than operation.
  673. """
  674. return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool)
  675. @var_operation
  676. def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
  677. """Check if a string is greater than another string.
  678. Args:
  679. lhs: The left-hand side string.
  680. rhs: The right-hand side string.
  681. Returns:
  682. The string greater than operation.
  683. """
  684. return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool)
  685. @var_operation
  686. def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
  687. """Check if a string is less than or equal to another string.
  688. Args:
  689. lhs: The left-hand side string.
  690. rhs: The right-hand side string.
  691. Returns:
  692. The string less than or equal operation.
  693. """
  694. return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool)
  695. @var_operation
  696. def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
  697. """Check if a string is greater than or equal to another string.
  698. Args:
  699. lhs: The left-hand side string.
  700. rhs: The right-hand side string.
  701. Returns:
  702. The string greater than or equal operation.
  703. """
  704. return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool)
  705. @var_operation
  706. def string_lower_operation(string: StringVar[Any]):
  707. """Convert a string to lowercase.
  708. Args:
  709. string: The string to convert.
  710. Returns:
  711. The lowercase string.
  712. """
  713. return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str)
  714. @var_operation
  715. def string_upper_operation(string: StringVar[Any]):
  716. """Convert a string to uppercase.
  717. Args:
  718. string: The string to convert.
  719. Returns:
  720. The uppercase string.
  721. """
  722. return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str)
  723. @var_operation
  724. def string_title_operation(string: StringVar[Any]):
  725. """Convert a string to title case.
  726. Args:
  727. string: The string to convert.
  728. Returns:
  729. The title case string.
  730. """
  731. return var_operation_return(
  732. js_expression=f"{string}.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')",
  733. var_type=str,
  734. )
  735. @var_operation
  736. def string_capitalize_operation(string: StringVar[Any]):
  737. """Capitalize a string.
  738. Args:
  739. string: The string to capitalize.
  740. Returns:
  741. The capitalized string.
  742. """
  743. return var_operation_return(
  744. js_expression=f"(((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())({string}))",
  745. var_type=str,
  746. )
  747. @var_operation
  748. def string_strip_operation(string: StringVar[Any]):
  749. """Strip a string.
  750. Args:
  751. string: The string to strip.
  752. Returns:
  753. The stripped string.
  754. """
  755. return var_operation_return(js_expression=f"{string}.trim()", var_type=str)
  756. @var_operation
  757. def string_contains_field_operation(
  758. haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str
  759. ):
  760. """Check if a string contains another string.
  761. Args:
  762. haystack: The haystack.
  763. needle: The needle.
  764. field: The field to check.
  765. Returns:
  766. The string contains operation.
  767. """
  768. return var_operation_return(
  769. js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})",
  770. var_type=bool,
  771. )
  772. @var_operation
  773. def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str):
  774. """Check if a string contains another string.
  775. Args:
  776. haystack: The haystack.
  777. needle: The needle.
  778. Returns:
  779. The string contains operation.
  780. """
  781. return var_operation_return(
  782. js_expression=f"{haystack}.includes({needle})", var_type=bool
  783. )
  784. @var_operation
  785. def string_starts_with_operation(
  786. full_string: StringVar[Any], prefix: StringVar[Any] | str
  787. ):
  788. """Check if a string starts with a prefix.
  789. Args:
  790. full_string: The full string.
  791. prefix: The prefix.
  792. Returns:
  793. Whether the string starts with the prefix.
  794. """
  795. return var_operation_return(
  796. js_expression=f"{full_string}.startsWith({prefix})", var_type=bool
  797. )
  798. @var_operation
  799. def string_ends_with_operation(
  800. full_string: StringVar[Any], suffix: StringVar[Any] | str
  801. ):
  802. """Check if a string ends with a suffix.
  803. Args:
  804. full_string: The full string.
  805. suffix: The suffix.
  806. Returns:
  807. Whether the string ends with the suffix.
  808. """
  809. return var_operation_return(
  810. js_expression=f"{full_string}.endsWith({suffix})", var_type=bool
  811. )
  812. @var_operation
  813. def string_item_operation(string: StringVar[Any], index: NumberVar | int):
  814. """Get an item from a string.
  815. Args:
  816. string: The string.
  817. index: The index of the item.
  818. Returns:
  819. The item from the string.
  820. """
  821. return var_operation_return(js_expression=f"{string}.at({index})", var_type=str)
  822. @var_operation
  823. def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
  824. """Join the elements of an array.
  825. Args:
  826. array: The array.
  827. sep: The separator.
  828. Returns:
  829. The joined elements.
  830. """
  831. return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str)
  832. @var_operation
  833. def string_replace_operation(
  834. string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str
  835. ):
  836. """Replace a string with a value.
  837. Args:
  838. string: The string.
  839. search_value: The string to search.
  840. new_value: The value to be replaced with.
  841. Returns:
  842. The string replace operation.
  843. """
  844. return var_operation_return(
  845. js_expression=f"{string}.replaceAll({search_value}, {new_value})",
  846. var_type=str,
  847. )
  848. @var_operation
  849. def get_decimal_string_separator_operation(value: NumberVar, separator: StringVar):
  850. """Get the decimal string separator.
  851. Args:
  852. value: The number.
  853. separator: The separator.
  854. Returns:
  855. The decimal string separator.
  856. """
  857. return var_operation_return(
  858. js_expression=f"({value}.toLocaleString('en-US').replaceAll(',', {separator}))",
  859. var_type=str,
  860. )
  861. @var_operation
  862. def get_decimal_string_operation(
  863. value: NumberVar, decimals: NumberVar, separator: StringVar
  864. ):
  865. """Get the decimal string of the number.
  866. Args:
  867. value: The number.
  868. decimals: The number of decimals.
  869. separator: The separator.
  870. Returns:
  871. The decimal string of the number.
  872. """
  873. return var_operation_return(
  874. js_expression=f"({value}.toLocaleString('en-US', ((decimals) => ({{minimumFractionDigits: decimals, maximumFractionDigits: decimals}}))({decimals})).replaceAll(',', {separator}))",
  875. var_type=str,
  876. )
  877. # Compile regex for finding reflex var tags.
  878. _decode_var_pattern_re = (
  879. rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
  880. )
  881. _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
  882. @dataclasses.dataclass(
  883. eq=False,
  884. frozen=True,
  885. slots=True,
  886. )
  887. class LiteralStringVar(LiteralVar, StringVar[str]):
  888. """Base class for immutable literal string vars."""
  889. _var_value: str = dataclasses.field(default="")
  890. @classmethod
  891. def create(
  892. cls,
  893. value: str,
  894. _var_type: GenericType | None = None,
  895. _var_data: VarData | None = None,
  896. ) -> StringVar:
  897. """Create a var from a string value.
  898. Args:
  899. value: The value to create the var from.
  900. _var_type: The type of the var.
  901. _var_data: Additional hooks and imports associated with the Var.
  902. Returns:
  903. The var.
  904. """
  905. # Determine var type in case the value is inherited from str.
  906. _var_type = _var_type or type(value) or str
  907. if REFLEX_VAR_OPENING_TAG in value:
  908. strings_and_vals: list[Var | str] = []
  909. offset = 0
  910. # Find all tags
  911. while m := _decode_var_pattern.search(value):
  912. start, end = m.span()
  913. strings_and_vals.append(value[:start])
  914. serialized_data = m.group(1)
  915. if serialized_data.isnumeric() or (
  916. serialized_data[0] == "-" and serialized_data[1:].isnumeric()
  917. ):
  918. # This is a global immutable var.
  919. var = _global_vars[int(serialized_data)]
  920. strings_and_vals.append(var)
  921. value = value[(end + len(var._js_expr)) :]
  922. offset += end - start
  923. strings_and_vals.append(value)
  924. filtered_strings_and_vals = [
  925. s for s in strings_and_vals if isinstance(s, Var) or s
  926. ]
  927. if len(filtered_strings_and_vals) == 1:
  928. only_string = filtered_strings_and_vals[0]
  929. if isinstance(only_string, str):
  930. return LiteralVar.create(only_string).to(StringVar, _var_type)
  931. else:
  932. return only_string.to(StringVar, only_string._var_type)
  933. if len(
  934. literal_strings := [
  935. s
  936. for s in filtered_strings_and_vals
  937. if isinstance(s, (str, LiteralStringVar))
  938. ]
  939. ) == len(filtered_strings_and_vals):
  940. return LiteralStringVar.create(
  941. "".join(
  942. s._var_value if isinstance(s, LiteralStringVar) else s
  943. for s in literal_strings
  944. ),
  945. _var_type=_var_type,
  946. _var_data=VarData.merge(
  947. _var_data,
  948. *(
  949. s._get_all_var_data()
  950. for s in filtered_strings_and_vals
  951. if isinstance(s, Var)
  952. ),
  953. ),
  954. )
  955. concat_result = ConcatVarOperation.create(
  956. *filtered_strings_and_vals,
  957. _var_data=_var_data,
  958. )
  959. return (
  960. concat_result
  961. if _var_type is str
  962. else concat_result.to(StringVar, _var_type)
  963. )
  964. return LiteralStringVar(
  965. _js_expr=json.dumps(value),
  966. _var_type=_var_type,
  967. _var_data=_var_data,
  968. _var_value=value,
  969. )
  970. def __hash__(self) -> int:
  971. """Get the hash of the var.
  972. Returns:
  973. The hash of the var.
  974. """
  975. return hash((type(self).__name__, self._var_value))
  976. def json(self) -> str:
  977. """Get the JSON representation of the var.
  978. Returns:
  979. The JSON representation of the var.
  980. """
  981. return json.dumps(self._var_value)
  982. @dataclasses.dataclass(
  983. eq=False,
  984. frozen=True,
  985. slots=True,
  986. )
  987. class ConcatVarOperation(CachedVarOperation, StringVar[str]):
  988. """Representing a concatenation of literal string vars."""
  989. _var_value: tuple[Var, ...] = dataclasses.field(default_factory=tuple)
  990. @cached_property_no_lock
  991. def _cached_var_name(self) -> str:
  992. """The name of the var.
  993. Returns:
  994. The name of the var.
  995. """
  996. list_of_strs: list[str | Var] = []
  997. last_string = ""
  998. for var in self._var_value:
  999. if isinstance(var, LiteralStringVar):
  1000. last_string += var._var_value
  1001. else:
  1002. if last_string:
  1003. list_of_strs.append(last_string)
  1004. last_string = ""
  1005. list_of_strs.append(var)
  1006. if last_string:
  1007. list_of_strs.append(last_string)
  1008. list_of_strs_filtered = [
  1009. str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s
  1010. ]
  1011. if len(list_of_strs_filtered) == 1:
  1012. return list_of_strs_filtered[0]
  1013. return "(" + "+".join(list_of_strs_filtered) + ")"
  1014. @cached_property_no_lock
  1015. def _cached_get_all_var_data(self) -> VarData | None:
  1016. """Get all the VarData asVarDatae Var.
  1017. Returns:
  1018. The VarData associated with the Var.
  1019. """
  1020. return VarData.merge(
  1021. *[
  1022. var._get_all_var_data()
  1023. for var in self._var_value
  1024. if isinstance(var, Var)
  1025. ],
  1026. self._var_data,
  1027. )
  1028. @classmethod
  1029. def create(
  1030. cls,
  1031. *value: Var | str,
  1032. _var_data: VarData | None = None,
  1033. ) -> ConcatVarOperation:
  1034. """Create a var from a string value.
  1035. Args:
  1036. *value: The values to concatenate.
  1037. _var_data: Additional hooks and imports associated with the Var.
  1038. Returns:
  1039. The var.
  1040. """
  1041. return cls(
  1042. _js_expr="",
  1043. _var_type=str,
  1044. _var_data=_var_data,
  1045. _var_value=tuple(map(LiteralVar.create, value)),
  1046. )
  1047. @var_operation
  1048. def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""):
  1049. """Split a string.
  1050. Args:
  1051. string: The string to split.
  1052. sep: The separator.
  1053. Returns:
  1054. The split string.
  1055. """
  1056. return var_operation_return(
  1057. js_expression=f"{string}.split({sep})", var_type=list[str]
  1058. )
  1059. @dataclasses.dataclass(
  1060. eq=False,
  1061. frozen=True,
  1062. slots=True,
  1063. )
  1064. class ArraySliceOperation(CachedVarOperation, ArrayVar):
  1065. """Base class for immutable string vars that are the result of a string slice operation."""
  1066. _array: ArrayVar = dataclasses.field(
  1067. default_factory=lambda: LiteralArrayVar.create([])
  1068. )
  1069. _start: NumberVar | int = dataclasses.field(default_factory=lambda: 0)
  1070. _stop: NumberVar | int = dataclasses.field(default_factory=lambda: 0)
  1071. _step: NumberVar | int = dataclasses.field(default_factory=lambda: 1)
  1072. @cached_property_no_lock
  1073. def _cached_var_name(self) -> str:
  1074. """The name of the var.
  1075. Returns:
  1076. The name of the var.
  1077. Raises:
  1078. ValueError: If the slice step is zero.
  1079. """
  1080. start, end, step = self._start, self._stop, self._step
  1081. normalized_start = (
  1082. LiteralVar.create(start) if start is not None else Var(_js_expr="undefined")
  1083. )
  1084. normalized_end = (
  1085. LiteralVar.create(end) if end is not None else Var(_js_expr="undefined")
  1086. )
  1087. if step is None:
  1088. return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s})"
  1089. if not isinstance(step, Var):
  1090. if step < 0:
  1091. actual_start = end + 1 if end is not None else 0
  1092. actual_end = start + 1 if start is not None else self._array.length()
  1093. return str(self._array[actual_start:actual_end].reverse()[::-step])
  1094. if step == 0:
  1095. raise ValueError("slice step cannot be zero")
  1096. return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0)"
  1097. actual_start_reverse = end + 1 if end is not None else 0
  1098. actual_end_reverse = start + 1 if start is not None else self._array.length()
  1099. return f"{self.step!s} > 0 ? {self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0) : {self._array!s}.slice({actual_start_reverse!s}, {actual_end_reverse!s}).reverse().filter((_, i) => i % {-step!s} === 0)"
  1100. @classmethod
  1101. def create(
  1102. cls,
  1103. array: ArrayVar,
  1104. slice: slice,
  1105. _var_data: VarData | None = None,
  1106. ) -> ArraySliceOperation:
  1107. """Create a var from a string value.
  1108. Args:
  1109. array: The array.
  1110. slice: The slice.
  1111. _var_data: Additional hooks and imports associated with the Var.
  1112. Returns:
  1113. The var.
  1114. """
  1115. return cls(
  1116. _js_expr="",
  1117. _var_type=array._var_type,
  1118. _var_data=_var_data,
  1119. _array=array,
  1120. _start=slice.start,
  1121. _stop=slice.stop,
  1122. _step=slice.step,
  1123. )
  1124. @var_operation
  1125. def array_pluck_operation(
  1126. array: ArrayVar[ARRAY_VAR_TYPE],
  1127. field: StringVar | str,
  1128. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1129. """Pluck a field from an array of objects.
  1130. Args:
  1131. array: The array to pluck from.
  1132. field: The field to pluck from the objects in the array.
  1133. Returns:
  1134. The reversed array.
  1135. """
  1136. return var_operation_return(
  1137. js_expression=f"{array}.map(e=>e?.[{field}])",
  1138. var_type=array._var_type,
  1139. )
  1140. @var_operation
  1141. def array_reverse_operation(
  1142. array: ArrayVar[ARRAY_VAR_TYPE],
  1143. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1144. """Reverse an array.
  1145. Args:
  1146. array: The array to reverse.
  1147. Returns:
  1148. The reversed array.
  1149. """
  1150. return var_operation_return(
  1151. js_expression=f"{array}.slice().reverse()",
  1152. var_type=array._var_type,
  1153. )
  1154. @var_operation
  1155. def array_lt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1156. """Check if an array is less than another array.
  1157. Args:
  1158. lhs: The left-hand side array.
  1159. rhs: The right-hand side array.
  1160. Returns:
  1161. The array less than operation.
  1162. """
  1163. return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool)
  1164. @var_operation
  1165. def array_gt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1166. """Check if an array is greater than another array.
  1167. Args:
  1168. lhs: The left-hand side array.
  1169. rhs: The right-hand side array.
  1170. Returns:
  1171. The array greater than operation.
  1172. """
  1173. return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool)
  1174. @var_operation
  1175. def array_le_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1176. """Check if an array is less than or equal to another array.
  1177. Args:
  1178. lhs: The left-hand side array.
  1179. rhs: The right-hand side array.
  1180. Returns:
  1181. The array less than or equal operation.
  1182. """
  1183. return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool)
  1184. @var_operation
  1185. def array_ge_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1186. """Check if an array is greater than or equal to another array.
  1187. Args:
  1188. lhs: The left-hand side array.
  1189. rhs: The right-hand side array.
  1190. Returns:
  1191. The array greater than or equal operation.
  1192. """
  1193. return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool)
  1194. @var_operation
  1195. def array_length_operation(array: ArrayVar):
  1196. """Get the length of an array.
  1197. Args:
  1198. array: The array.
  1199. Returns:
  1200. The length of the array.
  1201. """
  1202. return var_operation_return(
  1203. js_expression=f"{array}.length",
  1204. var_type=int,
  1205. )
  1206. def is_tuple_type(t: GenericType) -> bool:
  1207. """Check if a type is a tuple type.
  1208. Args:
  1209. t: The type to check.
  1210. Returns:
  1211. Whether the type is a tuple type.
  1212. """
  1213. return get_origin(t) is tuple
  1214. def _determine_value_of_array_index(
  1215. var_type: GenericType, index: int | float | decimal.Decimal | None = None
  1216. ):
  1217. """Determine the value of an array index.
  1218. Args:
  1219. var_type: The type of the array.
  1220. index: The index of the array.
  1221. Returns:
  1222. The value of the array index.
  1223. """
  1224. origin_var_type = get_origin(var_type) or var_type
  1225. if origin_var_type in types.UnionTypes:
  1226. return unionize(
  1227. *[
  1228. _determine_value_of_array_index(t, index)
  1229. for t in get_args(var_type)
  1230. if t is not type(None)
  1231. ]
  1232. )
  1233. if origin_var_type is range:
  1234. return int
  1235. if origin_var_type in [
  1236. Sequence,
  1237. Iterable,
  1238. list,
  1239. set,
  1240. collections.abc.Sequence,
  1241. collections.abc.Iterable,
  1242. ]:
  1243. args = get_args(var_type)
  1244. return args[0] if args else Any
  1245. if origin_var_type is tuple:
  1246. args = get_args(var_type)
  1247. if len(args) == 2 and args[1] is ...:
  1248. return args[0]
  1249. return (
  1250. args[int(index) % len(args)]
  1251. if args and index is not None
  1252. else (unionize(*args) if args else Any)
  1253. )
  1254. return Any
  1255. @var_operation
  1256. def array_item_operation(array: ArrayVar, index: NumberVar | int):
  1257. """Get an item from an array.
  1258. Args:
  1259. array: The array.
  1260. index: The index of the item.
  1261. Returns:
  1262. The item from the array.
  1263. """
  1264. element_type = _determine_value_of_array_index(
  1265. array._var_type,
  1266. (
  1267. index
  1268. if isinstance(index, int)
  1269. else (index._var_value if isinstance(index, LiteralNumberVar) else None)
  1270. ),
  1271. )
  1272. return var_operation_return(
  1273. js_expression=f"{array!s}.at({index!s})",
  1274. var_type=element_type,
  1275. )
  1276. @var_operation
  1277. def array_range_operation(
  1278. start: NumberVar | int, stop: NumberVar | int, step: NumberVar | int
  1279. ):
  1280. """Create a range of numbers.
  1281. Args:
  1282. start: The start of the range.
  1283. stop: The end of the range.
  1284. step: The step of the range.
  1285. Returns:
  1286. The range of numbers.
  1287. """
  1288. return var_operation_return(
  1289. js_expression=f"Array.from({{ length: Math.ceil(({stop!s} - {start!s}) / {step!s}) }}, (_, i) => {start!s} + i * {step!s})",
  1290. var_type=list[int],
  1291. )
  1292. @var_operation
  1293. def array_contains_field_operation(
  1294. haystack: ArrayVar, needle: Any | Var, field: StringVar | str
  1295. ):
  1296. """Check if an array contains an element.
  1297. Args:
  1298. haystack: The array to check.
  1299. needle: The element to check for.
  1300. field: The field to check.
  1301. Returns:
  1302. The array contains operation.
  1303. """
  1304. return var_operation_return(
  1305. js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})",
  1306. var_type=bool,
  1307. )
  1308. @var_operation
  1309. def array_contains_operation(
  1310. haystack: ArrayVar, needle: Any | Var
  1311. ) -> CustomVarOperationReturn[bool]:
  1312. """Check if an array contains an element.
  1313. Args:
  1314. haystack: The array to check.
  1315. needle: The element to check for.
  1316. Returns:
  1317. The array contains operation.
  1318. """
  1319. return var_operation_return(
  1320. js_expression=f"{haystack}.includes({needle})",
  1321. var_type=bool,
  1322. )
  1323. @var_operation
  1324. def repeat_array_operation(
  1325. array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int
  1326. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1327. """Repeat an array a number of times.
  1328. Args:
  1329. array: The array to repeat.
  1330. count: The number of times to repeat the array.
  1331. Returns:
  1332. The repeated array.
  1333. """
  1334. return var_operation_return(
  1335. js_expression=f"Array.from({{ length: {count} }}).flatMap(() => {array})",
  1336. var_type=array._var_type,
  1337. )
  1338. @var_operation
  1339. def map_array_operation(
  1340. array: ArrayVar[ARRAY_VAR_TYPE],
  1341. function: FunctionVar,
  1342. ) -> CustomVarOperationReturn[list[Any]]:
  1343. """Map a function over an array.
  1344. Args:
  1345. array: The array.
  1346. function: The function to map.
  1347. Returns:
  1348. The mapped array.
  1349. """
  1350. return var_operation_return(
  1351. js_expression=f"{array}.map({function})", var_type=list[Any]
  1352. )
  1353. @var_operation
  1354. def array_concat_operation(
  1355. lhs: ArrayVar[ARRAY_VAR_TYPE], rhs: ArrayVar[ARRAY_VAR_TYPE]
  1356. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1357. """Concatenate two arrays.
  1358. Args:
  1359. lhs: The left-hand side array.
  1360. rhs: The right-hand side array.
  1361. Returns:
  1362. The concatenated array.
  1363. """
  1364. return var_operation_return(
  1365. js_expression=f"[...{lhs}, ...{rhs}]",
  1366. var_type=lhs._var_type | rhs._var_type,
  1367. )
  1368. class ColorVar(StringVar[Color], python_types=Color):
  1369. """Base class for immutable color vars."""
  1370. @dataclasses.dataclass(
  1371. eq=False,
  1372. frozen=True,
  1373. slots=True,
  1374. )
  1375. class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar):
  1376. """Base class for immutable literal color vars."""
  1377. _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black"))
  1378. @classmethod
  1379. def create(
  1380. cls,
  1381. value: Color,
  1382. _var_type: type[Color] | None = None,
  1383. _var_data: VarData | None = None,
  1384. ) -> ColorVar:
  1385. """Create a var from a string value.
  1386. Args:
  1387. value: The value to create the var from.
  1388. _var_type: The type of the var.
  1389. _var_data: Additional hooks and imports associated with the Var.
  1390. Returns:
  1391. The var.
  1392. """
  1393. return cls(
  1394. _js_expr="",
  1395. _var_type=_var_type or Color,
  1396. _var_data=_var_data,
  1397. _var_value=value,
  1398. )
  1399. def __hash__(self) -> int:
  1400. """Get the hash of the var.
  1401. Returns:
  1402. The hash of the var.
  1403. """
  1404. return hash(
  1405. (
  1406. self.__class__.__name__,
  1407. self._var_value.color,
  1408. self._var_value.alpha,
  1409. self._var_value.shade,
  1410. )
  1411. )
  1412. @cached_property_no_lock
  1413. def _cached_var_name(self) -> str:
  1414. """The name of the var.
  1415. Returns:
  1416. The name of the var.
  1417. """
  1418. alpha = self._var_value.alpha
  1419. alpha = (
  1420. ternary_operation(
  1421. alpha,
  1422. LiteralStringVar.create("a"),
  1423. LiteralStringVar.create(""),
  1424. )
  1425. if isinstance(alpha, Var)
  1426. else LiteralStringVar.create("a" if alpha else "")
  1427. )
  1428. shade = self._var_value.shade
  1429. shade = (
  1430. shade.to_string(use_json=False)
  1431. if isinstance(shade, Var)
  1432. else LiteralStringVar.create(str(shade))
  1433. )
  1434. return str(
  1435. ConcatVarOperation.create(
  1436. LiteralStringVar.create("var(--"),
  1437. self._var_value.color,
  1438. LiteralStringVar.create("-"),
  1439. alpha,
  1440. shade,
  1441. LiteralStringVar.create(")"),
  1442. )
  1443. )
  1444. @cached_property_no_lock
  1445. def _cached_get_all_var_data(self) -> VarData | None:
  1446. """Get all the var data.
  1447. Returns:
  1448. The var data.
  1449. """
  1450. return VarData.merge(
  1451. *[
  1452. LiteralVar.create(var)._get_all_var_data()
  1453. for var in (
  1454. self._var_value.color,
  1455. self._var_value.alpha,
  1456. self._var_value.shade,
  1457. )
  1458. ],
  1459. self._var_data,
  1460. )
  1461. def json(self) -> str:
  1462. """Get the JSON representation of the var.
  1463. Returns:
  1464. The JSON representation of the var.
  1465. Raises:
  1466. TypeError: If the color is not a valid color.
  1467. """
  1468. color, alpha, shade = map(
  1469. get_python_literal,
  1470. (self._var_value.color, self._var_value.alpha, self._var_value.shade),
  1471. )
  1472. if color is None or alpha is None or shade is None:
  1473. raise TypeError("Cannot serialize color that contains non-literal vars.")
  1474. if (
  1475. not isinstance(color, str)
  1476. or not isinstance(alpha, bool)
  1477. or not isinstance(shade, int)
  1478. ):
  1479. raise TypeError("Color is not a valid color.")
  1480. return f"var(--{color}-{'a' if alpha else ''}{shade})"
  1481. class RangeVar(ArrayVar[Sequence[int]], python_types=range):
  1482. """Base class for immutable range vars."""
  1483. @dataclasses.dataclass(
  1484. eq=False,
  1485. frozen=True,
  1486. slots=True,
  1487. )
  1488. class LiteralRangeVar(CachedVarOperation, LiteralVar, RangeVar):
  1489. """Base class for immutable literal range vars."""
  1490. _var_value: range = dataclasses.field(default_factory=lambda: range(0))
  1491. @classmethod
  1492. def create(
  1493. cls,
  1494. value: range,
  1495. _var_type: type[range] | None = None,
  1496. _var_data: VarData | None = None,
  1497. ) -> RangeVar:
  1498. """Create a var from a string value.
  1499. Args:
  1500. value: The value to create the var from.
  1501. _var_type: The type of the var.
  1502. _var_data: Additional hooks and imports associated with the Var.
  1503. Returns:
  1504. The var.
  1505. """
  1506. return cls(
  1507. _js_expr="",
  1508. _var_type=_var_type or range,
  1509. _var_data=_var_data,
  1510. _var_value=value,
  1511. )
  1512. def __hash__(self) -> int:
  1513. """Get the hash of the var.
  1514. Returns:
  1515. The hash of the var.
  1516. """
  1517. return hash(
  1518. (
  1519. self.__class__.__name__,
  1520. self._var_value.start,
  1521. self._var_value.stop,
  1522. self._var_value.step,
  1523. )
  1524. )
  1525. @cached_property_no_lock
  1526. def _cached_var_name(self) -> str:
  1527. """The name of the var.
  1528. Returns:
  1529. The name of the var.
  1530. """
  1531. return f"Array.from({{ length: Math.ceil(({self._var_value.stop!s} - {self._var_value.start!s}) / {self._var_value.step!s}) }}, (_, i) => {self._var_value.start!s} + i * {self._var_value.step!s})"
  1532. @cached_property_no_lock
  1533. def _cached_get_all_var_data(self) -> VarData | None:
  1534. """Get all the var data.
  1535. Returns:
  1536. The var data.
  1537. """
  1538. return self._var_data
  1539. def json(self) -> str:
  1540. """Get the JSON representation of the var.
  1541. Returns:
  1542. The JSON representation of the var.
  1543. """
  1544. return json.dumps(
  1545. list(self._var_value),
  1546. )