vars.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478
  1. """Define a state var."""
  2. from __future__ import annotations
  3. import contextlib
  4. import dis
  5. import json
  6. import random
  7. import string
  8. from abc import ABC
  9. from types import CodeType, FunctionType
  10. from typing import (
  11. TYPE_CHECKING,
  12. Any,
  13. Callable,
  14. Dict,
  15. List,
  16. Optional,
  17. Tuple,
  18. Type,
  19. Union,
  20. _GenericAlias, # type: ignore
  21. cast,
  22. get_type_hints,
  23. )
  24. from pydantic.fields import ModelField
  25. from reflex import constants
  26. from reflex.base import Base
  27. from reflex.utils import console, format, serializers, types
  28. if TYPE_CHECKING:
  29. from reflex.state import State
  30. # Set of unique variable names.
  31. USED_VARIABLES = set()
  32. # Supported operators for all types.
  33. ALL_OPS = ["==", "!=", "!==", "===", "&&", "||"]
  34. # Delimiters used between function args or operands.
  35. DELIMITERS = [","]
  36. # Mapping of valid operations for different type combinations.
  37. OPERATION_MAPPING = {
  38. (int, int): {
  39. "+",
  40. "-",
  41. "/",
  42. "//",
  43. "*",
  44. "%",
  45. "**",
  46. ">",
  47. "<",
  48. "<=",
  49. ">=",
  50. "|",
  51. "&",
  52. },
  53. (int, str): {"*"},
  54. (int, list): {"*"},
  55. (str, str): {"+", ">", "<", "<=", ">="},
  56. (float, float): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
  57. (float, int): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
  58. (list, list): {"+", ">", "<", "<=", ">="},
  59. }
  60. def get_unique_variable_name() -> str:
  61. """Get a unique variable name.
  62. Returns:
  63. The unique variable name.
  64. """
  65. name = "".join([random.choice(string.ascii_lowercase) for _ in range(8)])
  66. if name not in USED_VARIABLES:
  67. USED_VARIABLES.add(name)
  68. return name
  69. return get_unique_variable_name()
  70. class Var(ABC):
  71. """An abstract var."""
  72. # The name of the var.
  73. name: str
  74. # The type of the var.
  75. type_: Type
  76. # The name of the enclosing state.
  77. state: str = ""
  78. # Whether this is a local javascript variable.
  79. is_local: bool = False
  80. # Whether the var is a string literal.
  81. is_string: bool = False
  82. @classmethod
  83. def create(
  84. cls, value: Any, is_local: bool = True, is_string: bool = False
  85. ) -> Var | None:
  86. """Create a var from a value.
  87. Args:
  88. value: The value to create the var from.
  89. is_local: Whether the var is local.
  90. is_string: Whether the var is a string literal.
  91. Returns:
  92. The var.
  93. Raises:
  94. TypeError: If the value is JSON-unserializable.
  95. """
  96. # Check for none values.
  97. if value is None:
  98. return None
  99. # If the value is already a var, do nothing.
  100. if isinstance(value, Var):
  101. return value
  102. # Try to serialize the value.
  103. type_ = type(value)
  104. name = serializers.serialize(value)
  105. if name is None:
  106. raise TypeError(
  107. f"No JSON serializer found for var {value} of type {type_}."
  108. )
  109. name = name if isinstance(name, str) else format.json_dumps(name)
  110. return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
  111. @classmethod
  112. def create_safe(
  113. cls, value: Any, is_local: bool = True, is_string: bool = False
  114. ) -> Var:
  115. """Create a var from a value, guaranteeing that it is not None.
  116. Args:
  117. value: The value to create the var from.
  118. is_local: Whether the var is local.
  119. is_string: Whether the var is a string literal.
  120. Returns:
  121. The var.
  122. """
  123. var = cls.create(value, is_local=is_local, is_string=is_string)
  124. assert var is not None
  125. return var
  126. @classmethod
  127. def __class_getitem__(cls, type_: str) -> _GenericAlias:
  128. """Get a typed var.
  129. Args:
  130. type_: The type of the var.
  131. Returns:
  132. The var class item.
  133. """
  134. return _GenericAlias(cls, type_)
  135. def _decode(self) -> Any:
  136. """Decode Var as a python value.
  137. Note that Var with state set cannot be decoded python-side and will be
  138. returned as full_name.
  139. Returns:
  140. The decoded value or the Var name.
  141. """
  142. if self.state:
  143. return self.full_name
  144. if self.is_string:
  145. return self.name
  146. try:
  147. return json.loads(self.name)
  148. except ValueError:
  149. return self.name
  150. def equals(self, other: Var) -> bool:
  151. """Check if two vars are equal.
  152. Args:
  153. other: The other var to compare.
  154. Returns:
  155. Whether the vars are equal.
  156. """
  157. return (
  158. self.name == other.name
  159. and self.type_ == other.type_
  160. and self.state == other.state
  161. and self.is_local == other.is_local
  162. )
  163. def to_string(self, json: bool = True) -> Var:
  164. """Convert a var to a string.
  165. Args:
  166. json: Whether to convert to a JSON string.
  167. Returns:
  168. The stringified var.
  169. """
  170. fn = "JSON.stringify" if json else "String"
  171. return self.operation(fn=fn, type_=str)
  172. def __hash__(self) -> int:
  173. """Define a hash function for a var.
  174. Returns:
  175. The hash of the var.
  176. """
  177. return hash((self.name, str(self.type_)))
  178. def __str__(self) -> str:
  179. """Wrap the var so it can be used in templates.
  180. Returns:
  181. The wrapped var, i.e. {state.var}.
  182. """
  183. out = self.full_name if self.is_local else format.wrap(self.full_name, "{")
  184. if self.is_string:
  185. out = format.format_string(out)
  186. return out
  187. def __bool__(self) -> bool:
  188. """Raise exception if using Var in a boolean context.
  189. Raises:
  190. TypeError: when attempting to bool-ify the Var.
  191. """
  192. raise TypeError(
  193. f"Cannot convert Var {self.full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
  194. "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
  195. )
  196. def __iter__(self) -> Any:
  197. """Raise exception if using Var in an iterable context.
  198. Raises:
  199. TypeError: when attempting to iterate over the Var.
  200. """
  201. raise TypeError(
  202. f"Cannot iterate over Var {self.full_name!r}. Instead use `rx.foreach`."
  203. )
  204. def __format__(self, format_spec: str) -> str:
  205. """Format the var into a Javascript equivalent to an f-string.
  206. Args:
  207. format_spec: The format specifier (Ignored for now).
  208. Returns:
  209. The formatted var.
  210. """
  211. if self.is_local:
  212. return str(self)
  213. return f"${str(self)}"
  214. def __getitem__(self, i: Any) -> Var:
  215. """Index into a var.
  216. Args:
  217. i: The index to index into.
  218. Returns:
  219. The indexed var.
  220. Raises:
  221. TypeError: If the var is not indexable.
  222. """
  223. # Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
  224. if not (
  225. types._issubclass(self.type_, Union[List, Dict, Tuple, str])
  226. or types.is_dataframe(self.type_)
  227. ):
  228. if self.type_ == Any:
  229. raise TypeError(
  230. "Could not index into var of type Any. (If you are trying to index into a state var, "
  231. "add the correct type annotation to the var.)"
  232. )
  233. raise TypeError(
  234. f"Var {self.name} of type {self.type_} does not support indexing."
  235. )
  236. # The type of the indexed var.
  237. type_ = Any
  238. # Convert any vars to local vars.
  239. if isinstance(i, Var):
  240. i = BaseVar(name=i.name, type_=i.type_, state=i.state, is_local=True)
  241. # Handle list/tuple/str indexing.
  242. if types._issubclass(self.type_, Union[List, Tuple, str]):
  243. # List/Tuple/String indices must be ints, slices, or vars.
  244. if (
  245. not isinstance(i, types.get_args(Union[int, slice, Var]))
  246. or isinstance(i, Var)
  247. and not i.type_ == int
  248. ):
  249. raise TypeError("Index must be an integer or an integer var.")
  250. # Handle slices first.
  251. if isinstance(i, slice):
  252. # Get the start and stop indices.
  253. start = i.start or 0
  254. stop = i.stop or "undefined"
  255. # Use the slice function.
  256. return BaseVar(
  257. name=f"{self.name}.slice({start}, {stop})",
  258. type_=self.type_,
  259. state=self.state,
  260. is_local=self.is_local,
  261. )
  262. # Get the type of the indexed var.
  263. type_ = (
  264. types.get_args(self.type_)[0]
  265. if types.is_generic_alias(self.type_)
  266. else Any
  267. )
  268. # Use `at` to support negative indices.
  269. return BaseVar(
  270. name=f"{self.name}.at({i})",
  271. type_=type_,
  272. state=self.state,
  273. is_local=self.is_local,
  274. )
  275. # Dictionary / dataframe indexing.
  276. # Tuples are currently not supported as indexes.
  277. if (
  278. (types._issubclass(self.type_, Dict) or types.is_dataframe(self.type_))
  279. and not isinstance(i, types.get_args(Union[int, str, float, Var]))
  280. ) or (
  281. isinstance(i, Var)
  282. and not types._issubclass(i.type_, types.get_args(Union[int, str, float]))
  283. ):
  284. raise TypeError(
  285. "Index must be one of the following types: int, str, int or str Var"
  286. )
  287. # Get the type of the indexed var.
  288. if isinstance(i, str):
  289. i = format.wrap(i, '"')
  290. type_ = (
  291. types.get_args(self.type_)[1] if types.is_generic_alias(self.type_) else Any
  292. )
  293. # Use normal indexing here.
  294. return BaseVar(
  295. name=f"{self.name}[{i}]",
  296. type_=type_,
  297. state=self.state,
  298. is_local=self.is_local,
  299. )
  300. def __getattribute__(self, name: str) -> Var:
  301. """Get a var attribute.
  302. Args:
  303. name: The name of the attribute.
  304. Returns:
  305. The var attribute.
  306. Raises:
  307. AttributeError: If the var is wrongly annotated or can't find attribute.
  308. TypeError: If an annotation to the var isn't provided.
  309. """
  310. try:
  311. return super().__getattribute__(name)
  312. except Exception as e:
  313. # Check if the attribute is one of the class fields.
  314. if not name.startswith("_"):
  315. if self.type_ == Any:
  316. raise TypeError(
  317. f"You must provide an annotation for the state var `{self.full_name}`. Annotation cannot be `{self.type_}`"
  318. ) from None
  319. if hasattr(self.type_, "__fields__") and name in self.type_.__fields__:
  320. type_ = self.type_.__fields__[name].outer_type_
  321. if isinstance(type_, ModelField):
  322. type_ = type_.type_
  323. return BaseVar(
  324. name=f"{self.name}.{name}",
  325. type_=type_,
  326. state=self.state,
  327. is_local=self.is_local,
  328. )
  329. raise AttributeError(
  330. f"The State var `{self.full_name}` has no attribute '{name}' or may have been annotated "
  331. f"wrongly.\n"
  332. f"original message: {e.args[0]}"
  333. ) from e
  334. def operation(
  335. self,
  336. op: str = "",
  337. other: Var | None = None,
  338. type_: Type | None = None,
  339. flip: bool = False,
  340. fn: str | None = None,
  341. invoke_fn: bool = False,
  342. ) -> Var:
  343. """Perform an operation on a var.
  344. Args:
  345. op: The operation to perform.
  346. other: The other var to perform the operation on.
  347. type_: The type of the operation result.
  348. flip: Whether to flip the order of the operation.
  349. fn: A function to apply to the operation.
  350. invoke_fn: Whether to invoke the function.
  351. Returns:
  352. The operation result.
  353. Raises:
  354. TypeError: If the operation between two operands is invalid.
  355. ValueError: If flip is set to true and value of operand is not provided
  356. """
  357. if isinstance(other, str):
  358. other = Var.create(json.dumps(other))
  359. else:
  360. other = Var.create(other)
  361. type_ = type_ or self.type_
  362. if other is None and flip:
  363. raise ValueError(
  364. "flip_operands cannot be set to True if the value of 'other' operand is not provided"
  365. )
  366. left_operand, right_operand = (other, self) if flip else (self, other)
  367. if other is not None:
  368. # check if the operation between operands is valid.
  369. if op and not self.is_valid_operation(
  370. types.get_base_class(left_operand.type_), # type: ignore
  371. types.get_base_class(right_operand.type_), # type: ignore
  372. op,
  373. ):
  374. raise TypeError(
  375. f"Unsupported Operand type(s) for {op}: `{left_operand.full_name}` of type {left_operand.type_.__name__} and `{right_operand.full_name}` of type {right_operand.type_.__name__}" # type: ignore
  376. )
  377. # apply function to operands
  378. if fn is not None:
  379. if invoke_fn:
  380. # invoke the function on left operand.
  381. operation_name = f"{left_operand.full_name}.{fn}({right_operand.full_name})" # type: ignore
  382. else:
  383. # pass the operands as arguments to the function.
  384. operation_name = f"{left_operand.full_name} {op} {right_operand.full_name}" # type: ignore
  385. operation_name = f"{fn}({operation_name})"
  386. else:
  387. # apply operator to operands (left operand <operator> right_operand)
  388. operation_name = f"{left_operand.full_name} {op} {right_operand.full_name}" # type: ignore
  389. operation_name = format.wrap(operation_name, "(")
  390. else:
  391. # apply operator to left operand (<operator> left_operand)
  392. operation_name = f"{op}{self.full_name}"
  393. # apply function to operands
  394. if fn is not None:
  395. operation_name = (
  396. f"{fn}({operation_name})"
  397. if not invoke_fn
  398. else f"{self.full_name}.{fn}()"
  399. )
  400. return BaseVar(
  401. name=operation_name,
  402. type_=type_,
  403. is_local=self.is_local,
  404. )
  405. @staticmethod
  406. def is_valid_operation(
  407. operand1_type: Type, operand2_type: Type, operator: str
  408. ) -> bool:
  409. """Check if an operation between two operands is valid.
  410. Args:
  411. operand1_type: Type of the operand
  412. operand2_type: Type of the second operand
  413. operator: The operator.
  414. Returns:
  415. Whether operation is valid or not
  416. """
  417. if operator in ALL_OPS or operator in DELIMITERS:
  418. return True
  419. # bools are subclasses of ints
  420. pair = tuple(
  421. sorted(
  422. [
  423. int if operand1_type == bool else operand1_type,
  424. int if operand2_type == bool else operand2_type,
  425. ],
  426. key=lambda x: x.__name__,
  427. )
  428. )
  429. return pair in OPERATION_MAPPING and operator in OPERATION_MAPPING[pair]
  430. def compare(self, op: str, other: Var) -> Var:
  431. """Compare two vars with inequalities.
  432. Args:
  433. op: The comparison operator.
  434. other: The other var to compare with.
  435. Returns:
  436. The comparison result.
  437. """
  438. return self.operation(op, other, bool)
  439. def __invert__(self) -> Var:
  440. """Invert a var.
  441. Returns:
  442. The inverted var.
  443. """
  444. return self.operation("!", type_=bool)
  445. def __neg__(self) -> Var:
  446. """Negate a var.
  447. Returns:
  448. The negated var.
  449. """
  450. return self.operation(fn="-")
  451. def __abs__(self) -> Var:
  452. """Get the absolute value of a var.
  453. Returns:
  454. A var with the absolute value.
  455. """
  456. return self.operation(fn="Math.abs")
  457. def length(self) -> Var:
  458. """Get the length of a list var.
  459. Returns:
  460. A var with the absolute value.
  461. Raises:
  462. TypeError: If the var is not a list.
  463. """
  464. if not types._issubclass(self.type_, List):
  465. raise TypeError(f"Cannot get length of non-list var {self}.")
  466. return BaseVar(
  467. name=f"{self.full_name}.length",
  468. type_=int,
  469. is_local=self.is_local,
  470. )
  471. def __eq__(self, other: Var) -> Var:
  472. """Perform an equality comparison.
  473. Args:
  474. other: The other var to compare with.
  475. Returns:
  476. A var representing the equality comparison.
  477. """
  478. return self.compare("===", other)
  479. def __ne__(self, other: Var) -> Var:
  480. """Perform an inequality comparison.
  481. Args:
  482. other: The other var to compare with.
  483. Returns:
  484. A var representing the inequality comparison.
  485. """
  486. return self.compare("!==", other)
  487. def __gt__(self, other: Var) -> Var:
  488. """Perform a greater than comparison.
  489. Args:
  490. other: The other var to compare with.
  491. Returns:
  492. A var representing the greater than comparison.
  493. """
  494. return self.compare(">", other)
  495. def __ge__(self, other: Var) -> Var:
  496. """Perform a greater than or equal to comparison.
  497. Args:
  498. other: The other var to compare with.
  499. Returns:
  500. A var representing the greater than or equal to comparison.
  501. """
  502. return self.compare(">=", other)
  503. def __lt__(self, other: Var) -> Var:
  504. """Perform a less than comparison.
  505. Args:
  506. other: The other var to compare with.
  507. Returns:
  508. A var representing the less than comparison.
  509. """
  510. return self.compare("<", other)
  511. def __le__(self, other: Var) -> Var:
  512. """Perform a less than or equal to comparison.
  513. Args:
  514. other: The other var to compare with.
  515. Returns:
  516. A var representing the less than or equal to comparison.
  517. """
  518. return self.compare("<=", other)
  519. def __add__(self, other: Var, flip=False) -> Var:
  520. """Add two vars.
  521. Args:
  522. other: The other var to add.
  523. flip: Whether to flip operands.
  524. Returns:
  525. A var representing the sum.
  526. """
  527. other_type = other.type_ if isinstance(other, Var) else type(other)
  528. # For list-list addition, javascript concatenates the content of the lists instead of
  529. # merging the list, and for that reason we use the spread operator available through spreadArraysOrObjects
  530. # utility function
  531. if (
  532. types.get_base_class(self.type_) == list
  533. and types.get_base_class(other_type) == list
  534. ):
  535. return self.operation(",", other, fn="spreadArraysOrObjects", flip=flip)
  536. return self.operation("+", other, flip=flip)
  537. def __radd__(self, other: Var) -> Var:
  538. """Add two vars.
  539. Args:
  540. other: The other var to add.
  541. Returns:
  542. A var representing the sum.
  543. """
  544. return self.__add__(other=other, flip=True)
  545. def __sub__(self, other: Var) -> Var:
  546. """Subtract two vars.
  547. Args:
  548. other: The other var to subtract.
  549. Returns:
  550. A var representing the difference.
  551. """
  552. return self.operation("-", other)
  553. def __rsub__(self, other: Var) -> Var:
  554. """Subtract two vars.
  555. Args:
  556. other: The other var to subtract.
  557. Returns:
  558. A var representing the difference.
  559. """
  560. return self.operation("-", other, flip=True)
  561. def __mul__(self, other: Var, flip=True) -> Var:
  562. """Multiply two vars.
  563. Args:
  564. other: The other var to multiply.
  565. flip: Whether to flip operands
  566. Returns:
  567. A var representing the product.
  568. """
  569. other_type = other.type_ if isinstance(other, Var) else type(other)
  570. # For str-int multiplication, we use the repeat function.
  571. # i.e "hello" * 2 is equivalent to "hello".repeat(2) in js.
  572. if (types.get_base_class(self.type_), types.get_base_class(other_type)) in [
  573. (int, str),
  574. (str, int),
  575. ]:
  576. return self.operation(other=other, fn="repeat", invoke_fn=True)
  577. # For list-int multiplication, we use the Array function.
  578. # i.e ["hello"] * 2 is equivalent to Array(2).fill().map(() => ["hello"]).flat() in js.
  579. if (types.get_base_class(self.type_), types.get_base_class(other_type)) in [
  580. (int, list),
  581. (list, int),
  582. ]:
  583. other_name = other.full_name if isinstance(other, Var) else other
  584. name = f"Array({other_name}).fill().map(() => {self.full_name}).flat()"
  585. return BaseVar(
  586. name=name,
  587. type_=str,
  588. is_local=self.is_local,
  589. )
  590. return self.operation("*", other)
  591. def __rmul__(self, other: Var) -> Var:
  592. """Multiply two vars.
  593. Args:
  594. other: The other var to multiply.
  595. Returns:
  596. A var representing the product.
  597. """
  598. return self.__mul__(other=other, flip=True)
  599. def __pow__(self, other: Var) -> Var:
  600. """Raise a var to a power.
  601. Args:
  602. other: The power to raise to.
  603. Returns:
  604. A var representing the power.
  605. """
  606. return self.operation(",", other, fn="Math.pow")
  607. def __rpow__(self, other: Var) -> Var:
  608. """Raise a var to a power.
  609. Args:
  610. other: The power to raise to.
  611. Returns:
  612. A var representing the power.
  613. """
  614. return self.operation(",", other, flip=True, fn="Math.pow")
  615. def __truediv__(self, other: Var) -> Var:
  616. """Divide two vars.
  617. Args:
  618. other: The other var to divide.
  619. Returns:
  620. A var representing the quotient.
  621. """
  622. return self.operation("/", other)
  623. def __rtruediv__(self, other: Var) -> Var:
  624. """Divide two vars.
  625. Args:
  626. other: The other var to divide.
  627. Returns:
  628. A var representing the quotient.
  629. """
  630. return self.operation("/", other, flip=True)
  631. def __floordiv__(self, other: Var) -> Var:
  632. """Divide two vars.
  633. Args:
  634. other: The other var to divide.
  635. Returns:
  636. A var representing the quotient.
  637. """
  638. return self.operation("/", other, fn="Math.floor")
  639. def __mod__(self, other: Var) -> Var:
  640. """Get the remainder of two vars.
  641. Args:
  642. other: The other var to divide.
  643. Returns:
  644. A var representing the remainder.
  645. """
  646. return self.operation("%", other)
  647. def __rmod__(self, other: Var) -> Var:
  648. """Get the remainder of two vars.
  649. Args:
  650. other: The other var to divide.
  651. Returns:
  652. A var representing the remainder.
  653. """
  654. return self.operation("%", other, flip=True)
  655. def __and__(self, other: Var) -> Var:
  656. """Perform a logical and.
  657. Args:
  658. other: The other var to perform the logical AND with.
  659. Returns:
  660. A var representing the logical AND.
  661. Note:
  662. This method provides behavior specific to JavaScript, where it returns the JavaScript
  663. equivalent code (using the '&&' operator) of a logical AND operation.
  664. In JavaScript, the
  665. logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
  666. by returning the equivalent code as a Var instance.
  667. In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
  668. it challenging to override the behavior entirely.
  669. Therefore, this method leverages the
  670. bitwise AND '__and__' operator for custom JavaScript-like behavior.
  671. Example:
  672. >>> var1 = Var.create(True)
  673. >>> var2 = Var.create(False)
  674. >>> js_code = var1 & var2
  675. >>> print(js_code.full_name)
  676. '(true && false)'
  677. """
  678. return self.operation("&&", other, type_=bool)
  679. def __rand__(self, other: Var) -> Var:
  680. """Perform a logical and.
  681. Args:
  682. other: The other var to perform the logical AND with.
  683. Returns:
  684. A var representing the logical AND.
  685. Note:
  686. This method provides behavior specific to JavaScript, where it returns the JavaScript
  687. equivalent code (using the '&&' operator) of a logical AND operation.
  688. In JavaScript, the
  689. logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
  690. by returning the equivalent code as a Var instance.
  691. In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
  692. it challenging to override the behavior entirely.
  693. Therefore, this method leverages the
  694. bitwise AND '__rand__' operator for custom JavaScript-like behavior.
  695. Example:
  696. >>> var1 = Var.create(True)
  697. >>> var2 = Var.create(False)
  698. >>> js_code = var1 & var2
  699. >>> print(js_code.full_name)
  700. '(false && true)'
  701. """
  702. return self.operation("&&", other, type_=bool, flip=True)
  703. def __or__(self, other: Var) -> Var:
  704. """Perform a logical or.
  705. Args:
  706. other: The other var to perform the logical or with.
  707. Returns:
  708. A var representing the logical or.
  709. Note:
  710. This method provides behavior specific to JavaScript, where it returns the JavaScript
  711. equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
  712. logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
  713. by returning the equivalent code as a Var instance.
  714. In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
  715. it challenging to override the behavior entirely. Therefore, this method leverages the
  716. bitwise OR '__or__' operator for custom JavaScript-like behavior.
  717. Example:
  718. >>> var1 = Var.create(True)
  719. >>> var2 = Var.create(False)
  720. >>> js_code = var1 | var2
  721. >>> print(js_code.full_name)
  722. '(true || false)'
  723. """
  724. return self.operation("||", other, type_=bool)
  725. def __ror__(self, other: Var) -> Var:
  726. """Perform a logical or.
  727. Args:
  728. other: The other var to perform the logical or with.
  729. Returns:
  730. A var representing the logical or.
  731. Note:
  732. This method provides behavior specific to JavaScript, where it returns the JavaScript
  733. equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
  734. logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
  735. by returning the equivalent code as a Var instance.
  736. In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
  737. it challenging to override the behavior entirely. Therefore, this method leverages the
  738. bitwise OR '__or__' operator for custom JavaScript-like behavior.
  739. Example:
  740. >>> var1 = Var.create(True)
  741. >>> var2 = Var.create(False)
  742. >>> js_code = var1 | var2
  743. >>> print(js_code)
  744. 'false || true'
  745. """
  746. return self.operation("||", other, type_=bool, flip=True)
  747. def __contains__(self, _: Any) -> Var:
  748. """Override the 'in' operator to alert the user that it is not supported.
  749. Raises:
  750. TypeError: the operation is not supported
  751. """
  752. raise TypeError(
  753. "'in' operator not supported for Var types, use Var.contains() instead."
  754. )
  755. def contains(self, other: Any) -> Var:
  756. """Check if a var contains the object `other`.
  757. Args:
  758. other: The object to check.
  759. Raises:
  760. TypeError: If the var is not a valid type: dict, list, tuple or str.
  761. Returns:
  762. A var representing the contain check.
  763. """
  764. if not (types._issubclass(self.type_, Union[dict, list, tuple, str])):
  765. raise TypeError(
  766. f"Var {self.full_name} of type {self.type_} does not support contains check."
  767. )
  768. method = (
  769. "hasOwnProperty" if types.get_base_class(self.type_) == dict else "includes"
  770. )
  771. if isinstance(other, str):
  772. other = Var.create(json.dumps(other), is_string=True)
  773. elif not isinstance(other, Var):
  774. other = Var.create(other)
  775. if types._issubclass(self.type_, Dict):
  776. return BaseVar(
  777. name=f"{self.full_name}.{method}({other.full_name})",
  778. type_=bool,
  779. is_local=self.is_local,
  780. )
  781. else: # str, list, tuple
  782. # For strings, the left operand must be a string.
  783. if types._issubclass(self.type_, str) and not types._issubclass(
  784. other.type_, str
  785. ):
  786. raise TypeError(
  787. f"'in <string>' requires string as left operand, not {other.type_}"
  788. )
  789. return BaseVar(
  790. name=f"{self.full_name}.includes({other.full_name})",
  791. type_=bool,
  792. is_local=self.is_local,
  793. )
  794. def reverse(self) -> Var:
  795. """Reverse a list var.
  796. Raises:
  797. TypeError: If the var is not a list.
  798. Returns:
  799. A var with the reversed list.
  800. """
  801. if not types._issubclass(self.type_, list):
  802. raise TypeError(f"Cannot reverse non-list var {self.full_name}.")
  803. return BaseVar(
  804. name=f"[...{self.full_name}].reverse()",
  805. type_=self.type_,
  806. is_local=self.is_local,
  807. )
  808. def lower(self) -> Var:
  809. """Convert a string var to lowercase.
  810. Returns:
  811. A var with the lowercase string.
  812. Raises:
  813. TypeError: If the var is not a string.
  814. """
  815. if not types._issubclass(self.type_, str):
  816. raise TypeError(
  817. f"Cannot convert non-string var {self.full_name} to lowercase."
  818. )
  819. return BaseVar(
  820. name=f"{self.full_name}.toLowerCase()",
  821. type_=str,
  822. is_local=self.is_local,
  823. )
  824. def upper(self) -> Var:
  825. """Convert a string var to uppercase.
  826. Returns:
  827. A var with the uppercase string.
  828. Raises:
  829. TypeError: If the var is not a string.
  830. """
  831. if not types._issubclass(self.type_, str):
  832. raise TypeError(
  833. f"Cannot convert non-string var {self.full_name} to uppercase."
  834. )
  835. return BaseVar(
  836. name=f"{self.full_name}.toUpperCase()",
  837. type_=str,
  838. is_local=self.is_local,
  839. )
  840. def split(self, other: str | Var[str] = " ") -> Var:
  841. """Split a string var into a list.
  842. Args:
  843. other: The string to split the var with.
  844. Returns:
  845. A var with the list.
  846. Raises:
  847. TypeError: If the var is not a string.
  848. """
  849. if not types._issubclass(self.type_, str):
  850. raise TypeError(f"Cannot split non-string var {self.full_name}.")
  851. other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
  852. return BaseVar(
  853. name=f"{self.full_name}.split({other.full_name})",
  854. type_=list[str],
  855. is_local=self.is_local,
  856. )
  857. def join(self, other: str | Var[str] | None = None) -> Var:
  858. """Join a list var into a string.
  859. Args:
  860. other: The string to join the list with.
  861. Returns:
  862. A var with the string.
  863. Raises:
  864. TypeError: If the var is not a list.
  865. """
  866. if not types._issubclass(self.type_, list):
  867. raise TypeError(f"Cannot join non-list var {self.full_name}.")
  868. if other is None:
  869. other = Var.create_safe("")
  870. if isinstance(other, str):
  871. other = Var.create_safe(json.dumps(other))
  872. else:
  873. other = Var.create_safe(other)
  874. return BaseVar(
  875. name=f"{self.full_name}.join({other.full_name})",
  876. type_=str,
  877. is_local=self.is_local,
  878. )
  879. def foreach(self, fn: Callable) -> Var:
  880. """Return a list of components. after doing a foreach on this var.
  881. Args:
  882. fn: The function to call on each component.
  883. Returns:
  884. A var representing foreach operation.
  885. """
  886. arg = BaseVar(
  887. name=get_unique_variable_name(),
  888. type_=self.type_,
  889. )
  890. return BaseVar(
  891. name=f"{self.full_name}.map(({arg.name}, i) => {fn(arg, key='i')})",
  892. type_=self.type_,
  893. is_local=self.is_local,
  894. )
  895. def to(self, type_: Type) -> Var:
  896. """Convert the type of the var.
  897. Args:
  898. type_: The type to convert to.
  899. Returns:
  900. The converted var.
  901. """
  902. return BaseVar(
  903. name=self.name,
  904. type_=type_,
  905. state=self.state,
  906. is_local=self.is_local,
  907. )
  908. @property
  909. def full_name(self) -> str:
  910. """Get the full name of the var.
  911. Returns:
  912. The full name of the var.
  913. """
  914. return self.name if self.state == "" else ".".join([self.state, self.name])
  915. def set_state(self, state: Type[State]) -> Any:
  916. """Set the state of the var.
  917. Args:
  918. state: The state to set.
  919. Returns:
  920. The var with the set state.
  921. """
  922. self.state = state.get_full_name()
  923. return self
  924. class BaseVar(Var, Base):
  925. """A base (non-computed) var of the app state."""
  926. # The name of the var.
  927. name: str
  928. # The type of the var.
  929. type_: Any
  930. # The name of the enclosing state.
  931. state: str = ""
  932. # Whether this is a local javascript variable.
  933. is_local: bool = False
  934. # Whether this var is a raw string.
  935. is_string: bool = False
  936. def __hash__(self) -> int:
  937. """Define a hash function for a var.
  938. Returns:
  939. The hash of the var.
  940. """
  941. return hash((self.name, str(self.type_)))
  942. def get_default_value(self) -> Any:
  943. """Get the default value of the var.
  944. Returns:
  945. The default value of the var.
  946. Raises:
  947. ImportError: If the var is a dataframe and pandas is not installed.
  948. """
  949. type_ = (
  950. self.type_.__origin__ if types.is_generic_alias(self.type_) else self.type_
  951. )
  952. if issubclass(type_, str):
  953. return ""
  954. if issubclass(type_, types.get_args(Union[int, float])):
  955. return 0
  956. if issubclass(type_, bool):
  957. return False
  958. if issubclass(type_, list):
  959. return []
  960. if issubclass(type_, dict):
  961. return {}
  962. if issubclass(type_, tuple):
  963. return ()
  964. if types.is_dataframe(type_):
  965. try:
  966. import pandas as pd
  967. return pd.DataFrame()
  968. except ImportError as e:
  969. raise ImportError(
  970. "Please install pandas to use dataframes in your app."
  971. ) from e
  972. return set() if issubclass(type_, set) else None
  973. def get_setter_name(self, include_state: bool = True) -> str:
  974. """Get the name of the var's generated setter function.
  975. Args:
  976. include_state: Whether to include the state name in the setter name.
  977. Returns:
  978. The name of the setter function.
  979. """
  980. setter = constants.SETTER_PREFIX + self.name
  981. if not include_state or self.state == "":
  982. return setter
  983. return ".".join((self.state, setter))
  984. def get_setter(self) -> Callable[[State, Any], None]:
  985. """Get the var's setter function.
  986. Returns:
  987. A function that that creates a setter for the var.
  988. """
  989. def setter(state: State, value: Any):
  990. """Get the setter for the var.
  991. Args:
  992. state: The state within which we add the setter function.
  993. value: The value to set.
  994. """
  995. if self.type_ in [int, float]:
  996. try:
  997. value = self.type_(value)
  998. setattr(state, self.name, value)
  999. except ValueError:
  1000. console.warn(
  1001. f"{self.name}: Failed conversion of {value} to '{self.type_.__name__}'. Value not set.",
  1002. )
  1003. else:
  1004. setattr(state, self.name, value)
  1005. setter.__qualname__ = self.get_setter_name()
  1006. return setter
  1007. class ComputedVar(Var, property):
  1008. """A field with computed getters."""
  1009. # Whether to track dependencies and cache computed values
  1010. cache: bool = False
  1011. @property
  1012. def name(self) -> str:
  1013. """Get the name of the var.
  1014. Returns:
  1015. The name of the var.
  1016. """
  1017. assert self.fget is not None, "Var must have a getter."
  1018. return self.fget.__name__
  1019. @property
  1020. def cache_attr(self) -> str:
  1021. """Get the attribute used to cache the value on the instance.
  1022. Returns:
  1023. An attribute name.
  1024. """
  1025. return f"__cached_{self.name}"
  1026. def __get__(self, instance, owner):
  1027. """Get the ComputedVar value.
  1028. If the value is already cached on the instance, return the cached value.
  1029. Args:
  1030. instance: the instance of the class accessing this computed var.
  1031. owner: the class that this descriptor is attached to.
  1032. Returns:
  1033. The value of the var for the given instance.
  1034. """
  1035. if instance is None or not self.cache:
  1036. return super().__get__(instance, owner)
  1037. # handle caching
  1038. if not hasattr(instance, self.cache_attr):
  1039. setattr(instance, self.cache_attr, super().__get__(instance, owner))
  1040. return getattr(instance, self.cache_attr)
  1041. def deps(
  1042. self,
  1043. objclass: Type,
  1044. obj: FunctionType | CodeType | None = None,
  1045. self_name: Optional[str] = None,
  1046. ) -> set[str]:
  1047. """Determine var dependencies of this ComputedVar.
  1048. Save references to attributes accessed on "self". Recursively called
  1049. when the function makes a method call on "self" or define comprehensions
  1050. or nested functions that may reference "self".
  1051. Args:
  1052. objclass: the class obj this ComputedVar is attached to.
  1053. obj: the object to disassemble (defaults to the fget function).
  1054. self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
  1055. Returns:
  1056. A set of variable names accessed by the given obj.
  1057. """
  1058. d = set()
  1059. if obj is None:
  1060. if self.fget is not None:
  1061. obj = cast(FunctionType, self.fget)
  1062. else:
  1063. return set()
  1064. with contextlib.suppress(AttributeError):
  1065. # unbox functools.partial
  1066. obj = cast(FunctionType, obj.func) # type: ignore
  1067. with contextlib.suppress(AttributeError):
  1068. # unbox EventHandler
  1069. obj = cast(FunctionType, obj.fn) # type: ignore
  1070. if self_name is None and isinstance(obj, FunctionType):
  1071. try:
  1072. # the first argument to the function is the name of "self" arg
  1073. self_name = obj.__code__.co_varnames[0]
  1074. except (AttributeError, IndexError):
  1075. self_name = None
  1076. if self_name is None:
  1077. # cannot reference attributes on self if method takes no args
  1078. return set()
  1079. self_is_top_of_stack = False
  1080. for instruction in dis.get_instructions(obj):
  1081. if (
  1082. instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
  1083. and instruction.argval == self_name
  1084. ):
  1085. # bytecode loaded the class instance to the top of stack, next load instruction
  1086. # is referencing an attribute on self
  1087. self_is_top_of_stack = True
  1088. continue
  1089. if self_is_top_of_stack and instruction.opname == "LOAD_ATTR":
  1090. # direct attribute access
  1091. d.add(instruction.argval)
  1092. elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD":
  1093. # method call on self
  1094. d.update(
  1095. self.deps(
  1096. objclass=objclass,
  1097. obj=getattr(objclass, instruction.argval),
  1098. )
  1099. )
  1100. elif instruction.opname == "LOAD_CONST" and isinstance(
  1101. instruction.argval, CodeType
  1102. ):
  1103. # recurse into nested functions / comprehensions, which can reference
  1104. # instance attributes from the outer scope
  1105. d.update(
  1106. self.deps(
  1107. objclass=objclass,
  1108. obj=instruction.argval,
  1109. self_name=self_name,
  1110. )
  1111. )
  1112. self_is_top_of_stack = False
  1113. return d
  1114. def mark_dirty(self, instance) -> None:
  1115. """Mark this ComputedVar as dirty.
  1116. Args:
  1117. instance: the state instance that needs to recompute the value.
  1118. """
  1119. with contextlib.suppress(AttributeError):
  1120. delattr(instance, self.cache_attr)
  1121. @property
  1122. def type_(self):
  1123. """Get the type of the var.
  1124. Returns:
  1125. The type of the var.
  1126. """
  1127. hints = get_type_hints(self.fget)
  1128. if "return" in hints:
  1129. return hints["return"]
  1130. return Any
  1131. def cached_var(fget: Callable[[Any], Any]) -> ComputedVar:
  1132. """A field with computed getter that tracks other state dependencies.
  1133. The cached_var will only be recalculated when other state vars that it
  1134. depends on are modified.
  1135. Args:
  1136. fget: the function that calculates the variable value.
  1137. Returns:
  1138. ComputedVar that is recomputed when dependencies change.
  1139. """
  1140. cvar = ComputedVar(fget=fget)
  1141. cvar.cache = True
  1142. return cvar
  1143. class ImportVar(Base):
  1144. """An import var."""
  1145. # The name of the import tag.
  1146. tag: Optional[str]
  1147. # whether the import is default or named.
  1148. is_default: Optional[bool] = False
  1149. # The tag alias.
  1150. alias: Optional[str] = None
  1151. # Whether this import need to install the associated lib
  1152. install: Optional[bool] = True
  1153. # whether this import should be rendered or not
  1154. render: Optional[bool] = True
  1155. @property
  1156. def name(self) -> str:
  1157. """The name of the import.
  1158. Returns:
  1159. The name(tag name with alias) of tag.
  1160. """
  1161. return self.tag if not self.alias else " as ".join([self.tag, self.alias]) # type: ignore
  1162. def __hash__(self) -> int:
  1163. """Define a hash function for the import var.
  1164. Returns:
  1165. The hash of the var.
  1166. """
  1167. return hash((self.tag, self.is_default, self.alias, self.install, self.render))
  1168. class NoRenderImportVar(ImportVar):
  1169. """A import that doesn't need to be rendered."""
  1170. render: Optional[bool] = False
  1171. def get_local_storage(key: Var | str | None = None) -> BaseVar:
  1172. """Provide a base var as payload to get local storage item(s).
  1173. Args:
  1174. key: Key to obtain value in the local storage.
  1175. Returns:
  1176. A BaseVar of the local storage method/function to call.
  1177. Raises:
  1178. TypeError: if the wrong key type is provided.
  1179. """
  1180. console.deprecate(
  1181. feature_name=f"rx.get_local_storage",
  1182. reason="and has been replaced by rx.LocalStorage, which can be used as a state var",
  1183. deprecation_version="0.2.9",
  1184. removal_version="0.2.10",
  1185. )
  1186. if key is not None:
  1187. if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str):
  1188. type_ = type(key) if not isinstance(key, Var) else key.type_
  1189. raise TypeError(
  1190. f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead."
  1191. )
  1192. key = key.full_name if isinstance(key, Var) else format.wrap(key, "'")
  1193. return BaseVar(name=f"localStorage.getItem({key})", type_=str)
  1194. return BaseVar(name="getAllLocalStorageItems()", type_=Dict)