sequence.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789
  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. @var_operation
  396. def string_replace_operation(
  397. string: StringVar, search_value: StringVar | str, new_value: StringVar | str
  398. ):
  399. """Replace a string with a value.
  400. Args:
  401. string: The string.
  402. search_value: The string to search.
  403. new_value: The value to be replaced with.
  404. Returns:
  405. The string replace operation.
  406. """
  407. return var_operation_return(
  408. js_expression=f"{string}.replace({search_value}, {new_value})",
  409. var_type=str,
  410. )
  411. # Compile regex for finding reflex var tags.
  412. _decode_var_pattern_re = (
  413. rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
  414. )
  415. _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
  416. @dataclasses.dataclass(
  417. eq=False,
  418. frozen=True,
  419. **{"slots": True} if sys.version_info >= (3, 10) else {},
  420. )
  421. class LiteralStringVar(LiteralVar, StringVar[str]):
  422. """Base class for immutable literal string vars."""
  423. _var_value: str = dataclasses.field(default="")
  424. @classmethod
  425. def create(
  426. cls,
  427. value: str,
  428. _var_type: GenericType | None = None,
  429. _var_data: VarData | None = None,
  430. ) -> StringVar:
  431. """Create a var from a string value.
  432. Args:
  433. value: The value to create the var from.
  434. _var_type: The type of the var.
  435. _var_data: Additional hooks and imports associated with the Var.
  436. Returns:
  437. The var.
  438. """
  439. # Determine var type in case the value is inherited from str.
  440. _var_type = _var_type or type(value) or str
  441. if REFLEX_VAR_OPENING_TAG in value:
  442. strings_and_vals: list[Var | str] = []
  443. offset = 0
  444. # Find all tags
  445. while m := _decode_var_pattern.search(value):
  446. start, end = m.span()
  447. strings_and_vals.append(value[:start])
  448. serialized_data = m.group(1)
  449. if serialized_data.isnumeric() or (
  450. serialized_data[0] == "-" and serialized_data[1:].isnumeric()
  451. ):
  452. # This is a global immutable var.
  453. var = _global_vars[int(serialized_data)]
  454. strings_and_vals.append(var)
  455. value = value[(end + len(var._js_expr)) :]
  456. offset += end - start
  457. strings_and_vals.append(value)
  458. filtered_strings_and_vals = [
  459. s for s in strings_and_vals if isinstance(s, Var) or s
  460. ]
  461. if len(filtered_strings_and_vals) == 1:
  462. only_string = filtered_strings_and_vals[0]
  463. if isinstance(only_string, str):
  464. return LiteralVar.create(only_string).to(StringVar, _var_type)
  465. else:
  466. return only_string.to(StringVar, only_string._var_type)
  467. if len(
  468. literal_strings := [
  469. s
  470. for s in filtered_strings_and_vals
  471. if isinstance(s, (str, LiteralStringVar))
  472. ]
  473. ) == len(filtered_strings_and_vals):
  474. return LiteralStringVar.create(
  475. "".join(
  476. s._var_value if isinstance(s, LiteralStringVar) else s
  477. for s in literal_strings
  478. ),
  479. _var_type=_var_type,
  480. _var_data=VarData.merge(
  481. _var_data,
  482. *(
  483. s._get_all_var_data()
  484. for s in filtered_strings_and_vals
  485. if isinstance(s, Var)
  486. ),
  487. ),
  488. )
  489. concat_result = ConcatVarOperation.create(
  490. *filtered_strings_and_vals,
  491. _var_data=_var_data,
  492. )
  493. return (
  494. concat_result
  495. if _var_type is str
  496. else concat_result.to(StringVar, _var_type)
  497. )
  498. return LiteralStringVar(
  499. _js_expr=json.dumps(value),
  500. _var_type=_var_type,
  501. _var_data=_var_data,
  502. _var_value=value,
  503. )
  504. def __hash__(self) -> int:
  505. """Get the hash of the var.
  506. Returns:
  507. The hash of the var.
  508. """
  509. return hash((self.__class__.__name__, self._var_value))
  510. def json(self) -> str:
  511. """Get the JSON representation of the var.
  512. Returns:
  513. The JSON representation of the var.
  514. """
  515. return json.dumps(self._var_value)
  516. @dataclasses.dataclass(
  517. eq=False,
  518. frozen=True,
  519. **{"slots": True} if sys.version_info >= (3, 10) else {},
  520. )
  521. class ConcatVarOperation(CachedVarOperation, StringVar[str]):
  522. """Representing a concatenation of literal string vars."""
  523. _var_value: Tuple[Var, ...] = dataclasses.field(default_factory=tuple)
  524. @cached_property_no_lock
  525. def _cached_var_name(self) -> str:
  526. """The name of the var.
  527. Returns:
  528. The name of the var.
  529. """
  530. list_of_strs: List[Union[str, Var]] = []
  531. last_string = ""
  532. for var in self._var_value:
  533. if isinstance(var, LiteralStringVar):
  534. last_string += var._var_value
  535. else:
  536. if last_string:
  537. list_of_strs.append(last_string)
  538. last_string = ""
  539. list_of_strs.append(var)
  540. if last_string:
  541. list_of_strs.append(last_string)
  542. list_of_strs_filtered = [
  543. str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s
  544. ]
  545. if len(list_of_strs_filtered) == 1:
  546. return list_of_strs_filtered[0]
  547. return "(" + "+".join(list_of_strs_filtered) + ")"
  548. @cached_property_no_lock
  549. def _cached_get_all_var_data(self) -> VarData | None:
  550. """Get all the VarData asVarDatae Var.
  551. Returns:
  552. The VarData associated with the Var.
  553. """
  554. return VarData.merge(
  555. *[
  556. var._get_all_var_data()
  557. for var in self._var_value
  558. if isinstance(var, Var)
  559. ],
  560. self._var_data,
  561. )
  562. @classmethod
  563. def create(
  564. cls,
  565. *value: Var | str,
  566. _var_data: VarData | None = None,
  567. ) -> ConcatVarOperation:
  568. """Create a var from a string value.
  569. Args:
  570. value: The values to concatenate.
  571. _var_data: Additional hooks and imports associated with the Var.
  572. Returns:
  573. The var.
  574. """
  575. return cls(
  576. _js_expr="",
  577. _var_type=str,
  578. _var_data=_var_data,
  579. _var_value=tuple(map(LiteralVar.create, value)),
  580. )
  581. ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Union[List, Tuple, Set])
  582. OTHER_TUPLE = TypeVar("OTHER_TUPLE")
  583. INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR")
  584. KEY_TYPE = TypeVar("KEY_TYPE")
  585. VALUE_TYPE = TypeVar("VALUE_TYPE")
  586. class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
  587. """Base class for immutable array vars."""
  588. @overload
  589. def join(self, sep: StringVar | str = "") -> StringVar: ...
  590. @overload
  591. def join(self, sep: NoReturn) -> NoReturn: ...
  592. def join(self, sep: Any = "") -> StringVar:
  593. """Join the elements of the array.
  594. Args:
  595. sep: The separator between elements.
  596. Returns:
  597. The joined elements.
  598. """
  599. if not isinstance(sep, (StringVar, str)):
  600. raise_unsupported_operand_types("join", (type(self), type(sep)))
  601. if (
  602. isinstance(self, LiteralArrayVar)
  603. and (
  604. len(
  605. args := [
  606. x
  607. for x in self._var_value
  608. if isinstance(x, (LiteralStringVar, str))
  609. ]
  610. )
  611. == len(self._var_value)
  612. )
  613. and isinstance(sep, (LiteralStringVar, str))
  614. ):
  615. sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep
  616. return LiteralStringVar.create(
  617. sep_str.join(
  618. i._var_value if isinstance(i, LiteralStringVar) else i for i in args
  619. )
  620. )
  621. return array_join_operation(self, sep)
  622. def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]:
  623. """Reverse the array.
  624. Returns:
  625. The reversed array.
  626. """
  627. return array_reverse_operation(self)
  628. @overload
  629. def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]: ...
  630. @overload
  631. def __add__(self, other: NoReturn) -> NoReturn: ...
  632. def __add__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
  633. """Concatenate two arrays.
  634. Parameters:
  635. other: The other array to concatenate.
  636. Returns:
  637. ArrayConcatOperation: The concatenation of the two arrays.
  638. """
  639. if not isinstance(other, ArrayVar):
  640. raise_unsupported_operand_types("+", (type(self), type(other)))
  641. return array_concat_operation(self, other)
  642. @overload
  643. def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ...
  644. @overload
  645. def __getitem__(
  646. self: (
  647. ArrayVar[Tuple[int, OTHER_TUPLE]]
  648. | ArrayVar[Tuple[float, OTHER_TUPLE]]
  649. | ArrayVar[Tuple[int | float, OTHER_TUPLE]]
  650. ),
  651. i: Literal[0, -2],
  652. ) -> NumberVar: ...
  653. @overload
  654. def __getitem__(
  655. self: (
  656. ArrayVar[Tuple[OTHER_TUPLE, int]]
  657. | ArrayVar[Tuple[OTHER_TUPLE, float]]
  658. | ArrayVar[Tuple[OTHER_TUPLE, int | float]]
  659. ),
  660. i: Literal[1, -1],
  661. ) -> NumberVar: ...
  662. @overload
  663. def __getitem__(
  664. self: ArrayVar[Tuple[str, OTHER_TUPLE]], i: Literal[0, -2]
  665. ) -> StringVar: ...
  666. @overload
  667. def __getitem__(
  668. self: ArrayVar[Tuple[OTHER_TUPLE, str]], i: Literal[1, -1]
  669. ) -> StringVar: ...
  670. @overload
  671. def __getitem__(
  672. self: ArrayVar[Tuple[bool, OTHER_TUPLE]], i: Literal[0, -2]
  673. ) -> BooleanVar: ...
  674. @overload
  675. def __getitem__(
  676. self: ArrayVar[Tuple[OTHER_TUPLE, bool]], i: Literal[1, -1]
  677. ) -> BooleanVar: ...
  678. @overload
  679. def __getitem__(
  680. self: (
  681. ARRAY_VAR_OF_LIST_ELEMENT[int]
  682. | ARRAY_VAR_OF_LIST_ELEMENT[float]
  683. | ARRAY_VAR_OF_LIST_ELEMENT[int | float]
  684. ),
  685. i: int | NumberVar,
  686. ) -> NumberVar: ...
  687. @overload
  688. def __getitem__(
  689. self: ARRAY_VAR_OF_LIST_ELEMENT[str], i: int | NumberVar
  690. ) -> StringVar: ...
  691. @overload
  692. def __getitem__(
  693. self: ARRAY_VAR_OF_LIST_ELEMENT[bool], i: int | NumberVar
  694. ) -> BooleanVar: ...
  695. @overload
  696. def __getitem__(
  697. self: ARRAY_VAR_OF_LIST_ELEMENT[List[INNER_ARRAY_VAR]],
  698. i: int | NumberVar,
  699. ) -> ArrayVar[List[INNER_ARRAY_VAR]]: ...
  700. @overload
  701. def __getitem__(
  702. self: ARRAY_VAR_OF_LIST_ELEMENT[Set[INNER_ARRAY_VAR]],
  703. i: int | NumberVar,
  704. ) -> ArrayVar[Set[INNER_ARRAY_VAR]]: ...
  705. @overload
  706. def __getitem__(
  707. self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[KEY_TYPE, VALUE_TYPE]],
  708. i: int | NumberVar,
  709. ) -> ArrayVar[Tuple[KEY_TYPE, VALUE_TYPE]]: ...
  710. @overload
  711. def __getitem__(
  712. self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[INNER_ARRAY_VAR, ...]],
  713. i: int | NumberVar,
  714. ) -> ArrayVar[Tuple[INNER_ARRAY_VAR, ...]]: ...
  715. @overload
  716. def __getitem__(
  717. self: ARRAY_VAR_OF_LIST_ELEMENT[Dict[KEY_TYPE, VALUE_TYPE]],
  718. i: int | NumberVar,
  719. ) -> ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]: ...
  720. @overload
  721. def __getitem__(self, i: int | NumberVar) -> Var: ...
  722. def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var:
  723. """Get a slice of the array.
  724. Args:
  725. i: The slice.
  726. Returns:
  727. The array slice operation.
  728. """
  729. if isinstance(i, slice):
  730. return ArraySliceOperation.create(self, i)
  731. if not isinstance(i, (int, NumberVar)) or (
  732. isinstance(i, NumberVar) and i._is_strict_float()
  733. ):
  734. raise_unsupported_operand_types("[]", (type(self), type(i)))
  735. return array_item_operation(self, i)
  736. def length(self) -> NumberVar:
  737. """Get the length of the array.
  738. Returns:
  739. The length of the array.
  740. """
  741. return array_length_operation(self)
  742. @overload
  743. @classmethod
  744. def range(cls, stop: int | NumberVar, /) -> ArrayVar[List[int]]: ...
  745. @overload
  746. @classmethod
  747. def range(
  748. cls,
  749. start: int | NumberVar,
  750. end: int | NumberVar,
  751. step: int | NumberVar = 1,
  752. /,
  753. ) -> ArrayVar[List[int]]: ...
  754. @overload
  755. @classmethod
  756. def range(
  757. cls,
  758. first_endpoint: int | NumberVar,
  759. second_endpoint: int | NumberVar | None = None,
  760. step: int | NumberVar | None = None,
  761. ) -> ArrayVar[List[int]]: ...
  762. @classmethod
  763. def range(
  764. cls,
  765. first_endpoint: int | NumberVar,
  766. second_endpoint: int | NumberVar | None = None,
  767. step: int | NumberVar | None = None,
  768. ) -> ArrayVar[List[int]]:
  769. """Create a range of numbers.
  770. Args:
  771. first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
  772. second_endpoint: The end of the range.
  773. step: The step of the range.
  774. Returns:
  775. The range of numbers.
  776. """
  777. if any(
  778. not isinstance(i, (int, NumberVar))
  779. for i in (first_endpoint, second_endpoint, step)
  780. if i is not None
  781. ):
  782. raise_unsupported_operand_types(
  783. "range", (type(first_endpoint), type(second_endpoint), type(step))
  784. )
  785. if second_endpoint is None:
  786. start = 0
  787. end = first_endpoint
  788. else:
  789. start = first_endpoint
  790. end = second_endpoint
  791. return array_range_operation(start, end, step or 1)
  792. @overload
  793. def contains(self, other: Any) -> BooleanVar: ...
  794. @overload
  795. def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ...
  796. def contains(self, other: Any, field: Any = None) -> BooleanVar:
  797. """Check if the array contains an element.
  798. Args:
  799. other: The element to check for.
  800. field: The field to check.
  801. Returns:
  802. The array contains operation.
  803. """
  804. if field is not None:
  805. if not isinstance(field, (StringVar, str)):
  806. raise_unsupported_operand_types("contains", (type(self), type(field)))
  807. return array_contains_field_operation(self, other, field)
  808. return array_contains_operation(self, other)
  809. def pluck(self, field: StringVar | str) -> ArrayVar:
  810. """Pluck a field from the array.
  811. Args:
  812. field: The field to pluck from the array.
  813. Returns:
  814. The array pluck operation.
  815. """
  816. return array_pluck_operation(self, field)
  817. @overload
  818. def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]: ...
  819. @overload
  820. def __mul__(self, other: NoReturn) -> NoReturn: ...
  821. def __mul__(self, other: Any) -> ArrayVar[ARRAY_VAR_TYPE]:
  822. """Multiply the sequence by a number or integer.
  823. Parameters:
  824. other: The number or integer to multiply the sequence by.
  825. Returns:
  826. ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
  827. """
  828. if not isinstance(other, (NumberVar, int)) or (
  829. isinstance(other, NumberVar) and other._is_strict_float()
  830. ):
  831. raise_unsupported_operand_types("*", (type(self), type(other)))
  832. return repeat_array_operation(self, other)
  833. __rmul__ = __mul__ # type: ignore
  834. @overload
  835. def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  836. @overload
  837. def __lt__(self, other: list | tuple) -> BooleanVar: ...
  838. def __lt__(self, other: Any):
  839. """Check if the array is less than another array.
  840. Args:
  841. other: The other array.
  842. Returns:
  843. The array less than operation.
  844. """
  845. if not isinstance(other, (ArrayVar, list, tuple)):
  846. raise_unsupported_operand_types("<", (type(self), type(other)))
  847. return array_lt_operation(self, other)
  848. @overload
  849. def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  850. @overload
  851. def __gt__(self, other: list | tuple) -> BooleanVar: ...
  852. def __gt__(self, other: Any):
  853. """Check if the array is greater than another array.
  854. Args:
  855. other: The other array.
  856. Returns:
  857. The array greater than operation.
  858. """
  859. if not isinstance(other, (ArrayVar, list, tuple)):
  860. raise_unsupported_operand_types(">", (type(self), type(other)))
  861. return array_gt_operation(self, other)
  862. @overload
  863. def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  864. @overload
  865. def __le__(self, other: list | tuple) -> BooleanVar: ...
  866. def __le__(self, other: Any):
  867. """Check if the array is less than or equal to another array.
  868. Args:
  869. other: The other array.
  870. Returns:
  871. The array less than or equal operation.
  872. """
  873. if not isinstance(other, (ArrayVar, list, tuple)):
  874. raise_unsupported_operand_types("<=", (type(self), type(other)))
  875. return array_le_operation(self, other)
  876. @overload
  877. def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ...
  878. @overload
  879. def __ge__(self, other: list | tuple) -> BooleanVar: ...
  880. def __ge__(self, other: Any):
  881. """Check if the array is greater than or equal to another array.
  882. Args:
  883. other: The other array.
  884. Returns:
  885. The array greater than or equal operation.
  886. """
  887. if not isinstance(other, (ArrayVar, list, tuple)):
  888. raise_unsupported_operand_types(">=", (type(self), type(other)))
  889. return array_ge_operation(self, other)
  890. def foreach(self, fn: Any):
  891. """Apply a function to each element of the array.
  892. Args:
  893. fn: The function to apply.
  894. Returns:
  895. The array after applying the function.
  896. Raises:
  897. VarTypeError: If the function takes more than one argument.
  898. """
  899. from .function import ArgsFunctionOperation
  900. if not callable(fn):
  901. raise_unsupported_operand_types("foreach", (type(self), type(fn)))
  902. # get the number of arguments of the function
  903. num_args = len(inspect.signature(fn).parameters)
  904. if num_args > 1:
  905. raise VarTypeError(
  906. "The function passed to foreach should take at most one argument."
  907. )
  908. if num_args == 0:
  909. return_value = fn()
  910. function_var = ArgsFunctionOperation.create(tuple(), return_value)
  911. else:
  912. # generic number var
  913. number_var = Var("").to(NumberVar, int)
  914. first_arg_type = self[number_var]._var_type
  915. arg_name = get_unique_variable_name()
  916. # get first argument type
  917. first_arg = Var(
  918. _js_expr=arg_name,
  919. _var_type=first_arg_type,
  920. ).guess_type()
  921. function_var = ArgsFunctionOperation.create(
  922. (arg_name,),
  923. Var.create(fn(first_arg)),
  924. )
  925. return map_array_operation(self, function_var)
  926. LIST_ELEMENT = TypeVar("LIST_ELEMENT")
  927. ARRAY_VAR_OF_LIST_ELEMENT = Union[
  928. ArrayVar[List[LIST_ELEMENT]],
  929. ArrayVar[Set[LIST_ELEMENT]],
  930. ArrayVar[Tuple[LIST_ELEMENT, ...]],
  931. ]
  932. @dataclasses.dataclass(
  933. eq=False,
  934. frozen=True,
  935. **{"slots": True} if sys.version_info >= (3, 10) else {},
  936. )
  937. class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
  938. """Base class for immutable literal array vars."""
  939. _var_value: Union[
  940. List[Union[Var, Any]],
  941. Set[Union[Var, Any]],
  942. Tuple[Union[Var, Any], ...],
  943. ] = dataclasses.field(default_factory=list)
  944. @cached_property_no_lock
  945. def _cached_var_name(self) -> str:
  946. """The name of the var.
  947. Returns:
  948. The name of the var.
  949. """
  950. return (
  951. "["
  952. + ", ".join(
  953. [str(LiteralVar.create(element)) for element in self._var_value]
  954. )
  955. + "]"
  956. )
  957. @cached_property_no_lock
  958. def _cached_get_all_var_data(self) -> VarData | None:
  959. """Get all the VarData associated with the Var.
  960. Returns:
  961. The VarData associated with the Var.
  962. """
  963. return VarData.merge(
  964. *[
  965. LiteralVar.create(element)._get_all_var_data()
  966. for element in self._var_value
  967. ],
  968. self._var_data,
  969. )
  970. def __hash__(self) -> int:
  971. """Get the hash of the var.
  972. Returns:
  973. The hash of the var.
  974. """
  975. return hash((self.__class__.__name__, self._js_expr))
  976. def json(self) -> str:
  977. """Get the JSON representation of the var.
  978. Returns:
  979. The JSON representation of the var.
  980. """
  981. return (
  982. "["
  983. + ", ".join(
  984. [LiteralVar.create(element).json() for element in self._var_value]
  985. )
  986. + "]"
  987. )
  988. @classmethod
  989. def create(
  990. cls,
  991. value: ARRAY_VAR_TYPE,
  992. _var_type: Type[ARRAY_VAR_TYPE] | None = None,
  993. _var_data: VarData | None = None,
  994. ) -> LiteralArrayVar[ARRAY_VAR_TYPE]:
  995. """Create a var from a string value.
  996. Args:
  997. value: The value to create the var from.
  998. _var_data: Additional hooks and imports associated with the Var.
  999. Returns:
  1000. The var.
  1001. """
  1002. return cls(
  1003. _js_expr="",
  1004. _var_type=figure_out_type(value) if _var_type is None else _var_type,
  1005. _var_data=_var_data,
  1006. _var_value=value,
  1007. )
  1008. @var_operation
  1009. def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""):
  1010. """Split a string.
  1011. Args:
  1012. string: The string to split.
  1013. sep: The separator.
  1014. Returns:
  1015. The split string.
  1016. """
  1017. return var_operation_return(
  1018. js_expression=f"{string}.split({sep})", var_type=List[str]
  1019. )
  1020. @dataclasses.dataclass(
  1021. eq=False,
  1022. frozen=True,
  1023. **{"slots": True} if sys.version_info >= (3, 10) else {},
  1024. )
  1025. class ArraySliceOperation(CachedVarOperation, ArrayVar):
  1026. """Base class for immutable string vars that are the result of a string slice operation."""
  1027. _array: ArrayVar = dataclasses.field(
  1028. default_factory=lambda: LiteralArrayVar.create([])
  1029. )
  1030. _start: NumberVar | int = dataclasses.field(default_factory=lambda: 0)
  1031. _stop: NumberVar | int = dataclasses.field(default_factory=lambda: 0)
  1032. _step: NumberVar | int = dataclasses.field(default_factory=lambda: 1)
  1033. @cached_property_no_lock
  1034. def _cached_var_name(self) -> str:
  1035. """The name of the var.
  1036. Returns:
  1037. The name of the var.
  1038. Raises:
  1039. ValueError: If the slice step is zero.
  1040. """
  1041. start, end, step = self._start, self._stop, self._step
  1042. normalized_start = (
  1043. LiteralVar.create(start) if start is not None else Var(_js_expr="undefined")
  1044. )
  1045. normalized_end = (
  1046. LiteralVar.create(end) if end is not None else Var(_js_expr="undefined")
  1047. )
  1048. if step is None:
  1049. return f"{str(self._array)}.slice({str(normalized_start)}, {str(normalized_end)})"
  1050. if not isinstance(step, Var):
  1051. if step < 0:
  1052. actual_start = end + 1 if end is not None else 0
  1053. actual_end = start + 1 if start is not None else self._array.length()
  1054. return str(self._array[actual_start:actual_end].reverse()[::-step])
  1055. if step == 0:
  1056. raise ValueError("slice step cannot be zero")
  1057. return f"{str(self._array)}.slice({str(normalized_start)}, {str(normalized_end)}).filter((_, i) => i % {str(step)} === 0)"
  1058. actual_start_reverse = end + 1 if end is not None else 0
  1059. actual_end_reverse = start + 1 if start is not None else self._array.length()
  1060. 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)"
  1061. @classmethod
  1062. def create(
  1063. cls,
  1064. array: ArrayVar,
  1065. slice: slice,
  1066. _var_data: VarData | None = None,
  1067. ) -> ArraySliceOperation:
  1068. """Create a var from a string value.
  1069. Args:
  1070. array: The array.
  1071. slice: The slice.
  1072. _var_data: Additional hooks and imports associated with the Var.
  1073. Returns:
  1074. The var.
  1075. """
  1076. return cls(
  1077. _js_expr="",
  1078. _var_type=array._var_type,
  1079. _var_data=_var_data,
  1080. _array=array,
  1081. _start=slice.start,
  1082. _stop=slice.stop,
  1083. _step=slice.step,
  1084. )
  1085. @var_operation
  1086. def array_pluck_operation(
  1087. array: ArrayVar[ARRAY_VAR_TYPE],
  1088. field: StringVar | str,
  1089. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1090. """Pluck a field from an array of objects.
  1091. Args:
  1092. array: The array to pluck from.
  1093. field: The field to pluck from the objects in the array.
  1094. Returns:
  1095. The reversed array.
  1096. """
  1097. return var_operation_return(
  1098. js_expression=f"{array}.map(e=>e?.[{field}])",
  1099. var_type=array._var_type,
  1100. )
  1101. @var_operation
  1102. def array_reverse_operation(
  1103. array: ArrayVar[ARRAY_VAR_TYPE],
  1104. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1105. """Reverse an array.
  1106. Args:
  1107. array: The array to reverse.
  1108. Returns:
  1109. The reversed array.
  1110. """
  1111. return var_operation_return(
  1112. js_expression=f"{array}.slice().reverse()",
  1113. var_type=array._var_type,
  1114. )
  1115. @var_operation
  1116. def array_lt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1117. """Check if an array is less than another array.
  1118. Args:
  1119. lhs: The left-hand side array.
  1120. rhs: The right-hand side array.
  1121. Returns:
  1122. The array less than operation.
  1123. """
  1124. return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool)
  1125. @var_operation
  1126. def array_gt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1127. """Check if an array is greater than another array.
  1128. Args:
  1129. lhs: The left-hand side array.
  1130. rhs: The right-hand side array.
  1131. Returns:
  1132. The array greater than operation.
  1133. """
  1134. return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool)
  1135. @var_operation
  1136. def array_le_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1137. """Check if an array is less than or equal to another array.
  1138. Args:
  1139. lhs: The left-hand side array.
  1140. rhs: The right-hand side array.
  1141. Returns:
  1142. The array less than or equal operation.
  1143. """
  1144. return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool)
  1145. @var_operation
  1146. def array_ge_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple):
  1147. """Check if an array is greater than or equal to another array.
  1148. Args:
  1149. lhs: The left-hand side array.
  1150. rhs: The right-hand side array.
  1151. Returns:
  1152. The array greater than or equal operation.
  1153. """
  1154. return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool)
  1155. @var_operation
  1156. def array_length_operation(array: ArrayVar):
  1157. """Get the length of an array.
  1158. Args:
  1159. array: The array.
  1160. Returns:
  1161. The length of the array.
  1162. """
  1163. return var_operation_return(
  1164. js_expression=f"{array}.length",
  1165. var_type=int,
  1166. )
  1167. def is_tuple_type(t: GenericType) -> bool:
  1168. """Check if a type is a tuple type.
  1169. Args:
  1170. t: The type to check.
  1171. Returns:
  1172. Whether the type is a tuple type.
  1173. """
  1174. if inspect.isclass(t):
  1175. return issubclass(t, tuple)
  1176. return get_origin(t) is tuple
  1177. @var_operation
  1178. def array_item_operation(array: ArrayVar, index: NumberVar | int):
  1179. """Get an item from an array.
  1180. Args:
  1181. array: The array.
  1182. index: The index of the item.
  1183. Returns:
  1184. The item from the array.
  1185. """
  1186. args = typing.get_args(array._var_type)
  1187. if args and isinstance(index, LiteralNumberVar) and is_tuple_type(array._var_type):
  1188. index_value = int(index._var_value)
  1189. element_type = args[index_value % len(args)]
  1190. else:
  1191. element_type = unionize(*args)
  1192. return var_operation_return(
  1193. js_expression=f"{str(array)}.at({str(index)})",
  1194. var_type=element_type,
  1195. )
  1196. @var_operation
  1197. def array_range_operation(
  1198. start: NumberVar | int, stop: NumberVar | int, step: NumberVar | int
  1199. ):
  1200. """Create a range of numbers.
  1201. Args:
  1202. start: The start of the range.
  1203. stop: The end of the range.
  1204. step: The step of the range.
  1205. Returns:
  1206. The range of numbers.
  1207. """
  1208. return var_operation_return(
  1209. js_expression=f"Array.from({{ length: ({str(stop)} - {str(start)}) / {str(step)} }}, (_, i) => {str(start)} + i * {str(step)})",
  1210. var_type=List[int],
  1211. )
  1212. @var_operation
  1213. def array_contains_field_operation(
  1214. haystack: ArrayVar, needle: Any | Var, field: StringVar | str
  1215. ):
  1216. """Check if an array contains an element.
  1217. Args:
  1218. haystack: The array to check.
  1219. needle: The element to check for.
  1220. field: The field to check.
  1221. Returns:
  1222. The array contains operation.
  1223. """
  1224. return var_operation_return(
  1225. js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})",
  1226. var_type=bool,
  1227. )
  1228. @var_operation
  1229. def array_contains_operation(haystack: ArrayVar, needle: Any | Var):
  1230. """Check if an array contains an element.
  1231. Args:
  1232. haystack: The array to check.
  1233. needle: The element to check for.
  1234. Returns:
  1235. The array contains operation.
  1236. """
  1237. return var_operation_return(
  1238. js_expression=f"{haystack}.includes({needle})",
  1239. var_type=bool,
  1240. )
  1241. @var_operation
  1242. def repeat_array_operation(
  1243. array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int
  1244. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1245. """Repeat an array a number of times.
  1246. Args:
  1247. array: The array to repeat.
  1248. count: The number of times to repeat the array.
  1249. Returns:
  1250. The repeated array.
  1251. """
  1252. return var_operation_return(
  1253. js_expression=f"Array.from({{ length: {count} }}).flatMap(() => {array})",
  1254. var_type=array._var_type,
  1255. )
  1256. if TYPE_CHECKING:
  1257. from .function import FunctionVar
  1258. @var_operation
  1259. def map_array_operation(
  1260. array: ArrayVar[ARRAY_VAR_TYPE],
  1261. function: FunctionVar,
  1262. ):
  1263. """Map a function over an array.
  1264. Args:
  1265. array: The array.
  1266. function: The function to map.
  1267. Returns:
  1268. The mapped array.
  1269. """
  1270. return var_operation_return(
  1271. js_expression=f"{array}.map({function})", var_type=List[Any]
  1272. )
  1273. @var_operation
  1274. def array_concat_operation(
  1275. lhs: ArrayVar[ARRAY_VAR_TYPE], rhs: ArrayVar[ARRAY_VAR_TYPE]
  1276. ) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]:
  1277. """Concatenate two arrays.
  1278. Args:
  1279. lhs: The left-hand side array.
  1280. rhs: The right-hand side array.
  1281. Returns:
  1282. The concatenated array.
  1283. """
  1284. return var_operation_return(
  1285. js_expression=f"[...{lhs}, ...{rhs}]",
  1286. var_type=Union[lhs._var_type, rhs._var_type],
  1287. )
  1288. class ColorVar(StringVar[Color], python_types=Color):
  1289. """Base class for immutable color vars."""
  1290. @dataclasses.dataclass(
  1291. eq=False,
  1292. frozen=True,
  1293. **{"slots": True} if sys.version_info >= (3, 10) else {},
  1294. )
  1295. class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar):
  1296. """Base class for immutable literal color vars."""
  1297. _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black"))
  1298. @classmethod
  1299. def create(
  1300. cls,
  1301. value: Color,
  1302. _var_type: Type[Color] | None = None,
  1303. _var_data: VarData | None = None,
  1304. ) -> ColorVar:
  1305. """Create a var from a string value.
  1306. Args:
  1307. value: The value to create the var from.
  1308. _var_type: The type of the var.
  1309. _var_data: Additional hooks and imports associated with the Var.
  1310. Returns:
  1311. The var.
  1312. """
  1313. return cls(
  1314. _js_expr="",
  1315. _var_type=_var_type or Color,
  1316. _var_data=_var_data,
  1317. _var_value=value,
  1318. )
  1319. def __hash__(self) -> int:
  1320. """Get the hash of the var.
  1321. Returns:
  1322. The hash of the var.
  1323. """
  1324. return hash(
  1325. (
  1326. self.__class__.__name__,
  1327. self._var_value.color,
  1328. self._var_value.alpha,
  1329. self._var_value.shade,
  1330. )
  1331. )
  1332. @cached_property_no_lock
  1333. def _cached_var_name(self) -> str:
  1334. """The name of the var.
  1335. Returns:
  1336. The name of the var.
  1337. """
  1338. alpha = self._var_value.alpha
  1339. alpha = (
  1340. ternary_operation(
  1341. alpha,
  1342. LiteralStringVar.create("a"),
  1343. LiteralStringVar.create(""),
  1344. )
  1345. if isinstance(alpha, Var)
  1346. else LiteralStringVar.create("a" if alpha else "")
  1347. )
  1348. shade = self._var_value.shade
  1349. shade = (
  1350. shade.to_string(use_json=False)
  1351. if isinstance(shade, Var)
  1352. else LiteralStringVar.create(str(shade))
  1353. )
  1354. return str(
  1355. ConcatVarOperation.create(
  1356. LiteralStringVar.create("var(--"),
  1357. self._var_value.color,
  1358. LiteralStringVar.create("-"),
  1359. alpha,
  1360. shade,
  1361. LiteralStringVar.create(")"),
  1362. )
  1363. )
  1364. @cached_property_no_lock
  1365. def _cached_get_all_var_data(self) -> VarData | None:
  1366. """Get all the var data.
  1367. Returns:
  1368. The var data.
  1369. """
  1370. return VarData.merge(
  1371. *[
  1372. LiteralVar.create(var)._get_all_var_data()
  1373. for var in (
  1374. self._var_value.color,
  1375. self._var_value.alpha,
  1376. self._var_value.shade,
  1377. )
  1378. ],
  1379. self._var_data,
  1380. )
  1381. def json(self) -> str:
  1382. """Get the JSON representation of the var.
  1383. Returns:
  1384. The JSON representation of the var.
  1385. Raises:
  1386. TypeError: If the color is not a valid color.
  1387. """
  1388. color, alpha, shade = map(
  1389. get_python_literal,
  1390. (self._var_value.color, self._var_value.alpha, self._var_value.shade),
  1391. )
  1392. if color is None or alpha is None or shade is None:
  1393. raise TypeError("Cannot serialize color that contains non-literal vars.")
  1394. if (
  1395. not isinstance(color, str)
  1396. or not isinstance(alpha, bool)
  1397. or not isinstance(shade, int)
  1398. ):
  1399. raise TypeError("Color is not a valid color.")
  1400. return f"var(--{color}-{'a' if alpha else ''}{shade})"