sequence.py 48 KB

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