sequence.py 52 KB

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