vars.py 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166
  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 FunctionType
  10. from typing import (
  11. TYPE_CHECKING,
  12. Any,
  13. Callable,
  14. Dict,
  15. List,
  16. Optional,
  17. Set,
  18. Type,
  19. Union,
  20. _GenericAlias, # type: ignore
  21. cast,
  22. get_type_hints,
  23. )
  24. from plotly.graph_objects import Figure
  25. from plotly.io import to_json
  26. from pydantic.fields import ModelField
  27. from reflex import constants
  28. from reflex.base import Base
  29. from reflex.utils import format, types
  30. if TYPE_CHECKING:
  31. from reflex.state import State
  32. # Set of unique variable names.
  33. USED_VARIABLES = set()
  34. def get_unique_variable_name() -> str:
  35. """Get a unique variable name.
  36. Returns:
  37. The unique variable name.
  38. """
  39. name = "".join([random.choice(string.ascii_lowercase) for _ in range(8)])
  40. if name not in USED_VARIABLES:
  41. USED_VARIABLES.add(name)
  42. return name
  43. return get_unique_variable_name()
  44. class Var(ABC):
  45. """An abstract var."""
  46. # The name of the var.
  47. name: str
  48. # The type of the var.
  49. type_: Type
  50. # The name of the enclosing state.
  51. state: str = ""
  52. # Whether this is a local javascript variable.
  53. is_local: bool = False
  54. # Whether the var is a string literal.
  55. is_string: bool = False
  56. @classmethod
  57. def create(
  58. cls, value: Any, is_local: bool = True, is_string: bool = False
  59. ) -> Optional[Var]:
  60. """Create a var from a value.
  61. Args:
  62. value: The value to create the var from.
  63. is_local: Whether the var is local.
  64. is_string: Whether the var is a string literal.
  65. Returns:
  66. The var.
  67. Raises:
  68. TypeError: If the value is JSON-unserializable.
  69. """
  70. # Check for none values.
  71. if value is None:
  72. return None
  73. # If the value is already a var, do nothing.
  74. if isinstance(value, Var):
  75. return value
  76. type_ = type(value)
  77. # Special case for plotly figures.
  78. if isinstance(value, Figure):
  79. value = json.loads(to_json(value))["data"] # type: ignore
  80. type_ = Figure
  81. if isinstance(value, dict):
  82. value = format.format_dict(value)
  83. try:
  84. name = value if isinstance(value, str) else json.dumps(value)
  85. except TypeError as e:
  86. raise TypeError(
  87. f"To create a Var must be Var or JSON-serializable. Got {value} of type {type(value)}."
  88. ) from e
  89. return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
  90. @classmethod
  91. def create_safe(
  92. cls, value: Any, is_local: bool = True, is_string: bool = False
  93. ) -> Var:
  94. """Create a var from a value, guaranteeing that it is not None.
  95. Args:
  96. value: The value to create the var from.
  97. is_local: Whether the var is local.
  98. is_string: Whether the var is a string literal.
  99. Returns:
  100. The var.
  101. """
  102. var = cls.create(value, is_local=is_local, is_string=is_string)
  103. assert var is not None
  104. return var
  105. @classmethod
  106. def __class_getitem__(cls, type_: str) -> _GenericAlias:
  107. """Get a typed var.
  108. Args:
  109. type_: The type of the var.
  110. Returns:
  111. The var class item.
  112. """
  113. return _GenericAlias(cls, type_)
  114. def equals(self, other: Var) -> bool:
  115. """Check if two vars are equal.
  116. Args:
  117. other: The other var to compare.
  118. Returns:
  119. Whether the vars are equal.
  120. """
  121. return (
  122. self.name == other.name
  123. and self.type_ == other.type_
  124. and self.state == other.state
  125. and self.is_local == other.is_local
  126. )
  127. def to_string(self) -> Var:
  128. """Convert a var to a string.
  129. Returns:
  130. The stringified var.
  131. """
  132. return self.operation(fn="JSON.stringify")
  133. def __hash__(self) -> int:
  134. """Define a hash function for a var.
  135. Returns:
  136. The hash of the var.
  137. """
  138. return hash((self.name, str(self.type_)))
  139. def __str__(self) -> str:
  140. """Wrap the var so it can be used in templates.
  141. Returns:
  142. The wrapped var, i.e. {state.var}.
  143. """
  144. out = self.full_name if self.is_local else format.wrap(self.full_name, "{")
  145. if self.is_string:
  146. out = format.format_string(out)
  147. return out
  148. def __getitem__(self, i: Any) -> Var:
  149. """Index into a var.
  150. Args:
  151. i: The index to index into.
  152. Returns:
  153. The indexed var.
  154. Raises:
  155. TypeError: If the var is not indexable.
  156. """
  157. # Indexing is only supported for lists, dicts, and dataframes.
  158. if not (
  159. types._issubclass(self.type_, Union[List, Dict])
  160. or types.is_dataframe(self.type_)
  161. ):
  162. if self.type_ == Any:
  163. raise TypeError(
  164. f"Could not index into var of type Any. (If you are trying to index into a state var, add the correct type annotation to the var.)"
  165. )
  166. raise TypeError(
  167. f"Var {self.name} of type {self.type_} does not support indexing."
  168. )
  169. # The type of the indexed var.
  170. type_ = Any
  171. # Convert any vars to local vars.
  172. if isinstance(i, Var):
  173. i = BaseVar(name=i.name, type_=i.type_, state=i.state, is_local=True)
  174. # Handle list indexing.
  175. if types._issubclass(self.type_, List):
  176. # List indices must be ints, slices, or vars.
  177. if not isinstance(i, types.get_args(Union[int, slice, Var])):
  178. raise TypeError("Index must be an integer.")
  179. # Handle slices first.
  180. if isinstance(i, slice):
  181. # Get the start and stop indices.
  182. start = i.start or 0
  183. stop = i.stop or "undefined"
  184. # Use the slice function.
  185. return BaseVar(
  186. name=f"{self.name}.slice({start}, {stop})",
  187. type_=self.type_,
  188. state=self.state,
  189. )
  190. # Get the type of the indexed var.
  191. type_ = (
  192. types.get_args(self.type_)[0]
  193. if types.is_generic_alias(self.type_)
  194. else Any
  195. )
  196. # Use `at` to support negative indices.
  197. return BaseVar(
  198. name=f"{self.name}.at({i})",
  199. type_=type_,
  200. state=self.state,
  201. )
  202. # Dictionary / dataframe indexing.
  203. # Get the type of the indexed var.
  204. if isinstance(i, str):
  205. i = format.wrap(i, '"')
  206. type_ = (
  207. types.get_args(self.type_)[1] if types.is_generic_alias(self.type_) else Any
  208. )
  209. # Use normal indexing here.
  210. return BaseVar(
  211. name=f"{self.name}[{i}]",
  212. type_=type_,
  213. state=self.state,
  214. )
  215. def __getattribute__(self, name: str) -> Var:
  216. """Get a var attribute.
  217. Args:
  218. name: The name of the attribute.
  219. Returns:
  220. The var attribute.
  221. Raises:
  222. AttributeError: If the var is wrongly annotated or can't find attribute.
  223. TypeError: If an annotation to the var isn't provided.
  224. """
  225. try:
  226. return super().__getattribute__(name)
  227. except Exception as e:
  228. # Check if the attribute is one of the class fields.
  229. if not name.startswith("_"):
  230. if self.type_ == Any:
  231. raise TypeError(
  232. f"You must provide an annotation for the state var `{self.full_name}`. Annotation cannot be `{self.type_}`"
  233. ) from None
  234. if hasattr(self.type_, "__fields__") and name in self.type_.__fields__:
  235. type_ = self.type_.__fields__[name].outer_type_
  236. if isinstance(type_, ModelField):
  237. type_ = type_.type_
  238. return BaseVar(
  239. name=f"{self.name}.{name}",
  240. type_=type_,
  241. state=self.state,
  242. )
  243. raise AttributeError(
  244. f"The State var `{self.full_name}` has no attribute '{name}' or may have been annotated "
  245. f"wrongly.\n"
  246. f"original message: {e.args[0]}"
  247. ) from e
  248. def operation(
  249. self,
  250. op: str = "",
  251. other: Optional[Var] = None,
  252. type_: Optional[Type] = None,
  253. flip: bool = False,
  254. fn: Optional[str] = None,
  255. ) -> Var:
  256. """Perform an operation on a var.
  257. Args:
  258. op: The operation to perform.
  259. other: The other var to perform the operation on.
  260. type_: The type of the operation result.
  261. flip: Whether to flip the order of the operation.
  262. fn: A function to apply to the operation.
  263. Returns:
  264. The operation result.
  265. """
  266. # Wrap strings in quotes.
  267. if isinstance(other, str):
  268. other = Var.create(json.dumps(other))
  269. else:
  270. other = Var.create(other)
  271. if type_ is None:
  272. type_ = self.type_
  273. if other is None:
  274. name = f"{op}{self.full_name}"
  275. else:
  276. props = (other, self) if flip else (self, other)
  277. name = f"{props[0].full_name} {op} {props[1].full_name}"
  278. if fn is None:
  279. name = format.wrap(name, "(")
  280. if fn is not None:
  281. name = f"{fn}({name})"
  282. return BaseVar(
  283. name=name,
  284. type_=type_,
  285. )
  286. def compare(self, op: str, other: Var) -> Var:
  287. """Compare two vars with inequalities.
  288. Args:
  289. op: The comparison operator.
  290. other: The other var to compare with.
  291. Returns:
  292. The comparison result.
  293. """
  294. return self.operation(op, other, bool)
  295. def __invert__(self) -> Var:
  296. """Invert a var.
  297. Returns:
  298. The inverted var.
  299. """
  300. return self.operation("!", type_=bool)
  301. def __neg__(self) -> Var:
  302. """Negate a var.
  303. Returns:
  304. The negated var.
  305. """
  306. return self.operation(fn="-")
  307. def __abs__(self) -> Var:
  308. """Get the absolute value of a var.
  309. Returns:
  310. A var with the absolute value.
  311. """
  312. return self.operation(fn="Math.abs")
  313. def length(self) -> Var:
  314. """Get the length of a list var.
  315. Returns:
  316. A var with the absolute value.
  317. Raises:
  318. TypeError: If the var is not a list.
  319. """
  320. if not types._issubclass(self.type_, List):
  321. raise TypeError(f"Cannot get length of non-list var {self}.")
  322. return BaseVar(
  323. name=f"{self.full_name}.length",
  324. type_=int,
  325. )
  326. def __eq__(self, other: Var) -> Var:
  327. """Perform an equality comparison.
  328. Args:
  329. other: The other var to compare with.
  330. Returns:
  331. A var representing the equality comparison.
  332. """
  333. return self.compare("===", other)
  334. def __ne__(self, other: Var) -> Var:
  335. """Perform an inequality comparison.
  336. Args:
  337. other: The other var to compare with.
  338. Returns:
  339. A var representing the inequality comparison.
  340. """
  341. return self.compare("!==", other)
  342. def __gt__(self, other: Var) -> Var:
  343. """Perform a greater than comparison.
  344. Args:
  345. other: The other var to compare with.
  346. Returns:
  347. A var representing the greater than comparison.
  348. """
  349. return self.compare(">", other)
  350. def __ge__(self, other: Var) -> Var:
  351. """Perform a greater than or equal to comparison.
  352. Args:
  353. other: The other var to compare with.
  354. Returns:
  355. A var representing the greater than or equal to comparison.
  356. """
  357. return self.compare(">=", other)
  358. def __lt__(self, other: Var) -> Var:
  359. """Perform a less than comparison.
  360. Args:
  361. other: The other var to compare with.
  362. Returns:
  363. A var representing the less than comparison.
  364. """
  365. return self.compare("<", other)
  366. def __le__(self, other: Var) -> Var:
  367. """Perform a less than or equal to comparison.
  368. Args:
  369. other: The other var to compare with.
  370. Returns:
  371. A var representing the less than or equal to comparison.
  372. """
  373. return self.compare("<=", other)
  374. def __add__(self, other: Var) -> Var:
  375. """Add two vars.
  376. Args:
  377. other: The other var to add.
  378. Returns:
  379. A var representing the sum.
  380. """
  381. return self.operation("+", other)
  382. def __radd__(self, other: Var) -> Var:
  383. """Add two vars.
  384. Args:
  385. other: The other var to add.
  386. Returns:
  387. A var representing the sum.
  388. """
  389. return self.operation("+", other, flip=True)
  390. def __sub__(self, other: Var) -> Var:
  391. """Subtract two vars.
  392. Args:
  393. other: The other var to subtract.
  394. Returns:
  395. A var representing the difference.
  396. """
  397. return self.operation("-", other)
  398. def __rsub__(self, other: Var) -> Var:
  399. """Subtract two vars.
  400. Args:
  401. other: The other var to subtract.
  402. Returns:
  403. A var representing the difference.
  404. """
  405. return self.operation("-", other, flip=True)
  406. def __mul__(self, other: Var) -> Var:
  407. """Multiply two vars.
  408. Args:
  409. other: The other var to multiply.
  410. Returns:
  411. A var representing the product.
  412. """
  413. return self.operation("*", other)
  414. def __rmul__(self, other: Var) -> Var:
  415. """Multiply two vars.
  416. Args:
  417. other: The other var to multiply.
  418. Returns:
  419. A var representing the product.
  420. """
  421. return self.operation("*", other, flip=True)
  422. def __pow__(self, other: Var) -> Var:
  423. """Raise a var to a power.
  424. Args:
  425. other: The power to raise to.
  426. Returns:
  427. A var representing the power.
  428. """
  429. return self.operation(",", other, fn="Math.pow")
  430. def __rpow__(self, other: Var) -> Var:
  431. """Raise a var to a power.
  432. Args:
  433. other: The power to raise to.
  434. Returns:
  435. A var representing the power.
  436. """
  437. return self.operation(",", other, flip=True, fn="Math.pow")
  438. def __truediv__(self, other: Var) -> Var:
  439. """Divide two vars.
  440. Args:
  441. other: The other var to divide.
  442. Returns:
  443. A var representing the quotient.
  444. """
  445. return self.operation("/", other)
  446. def __rtruediv__(self, other: Var) -> Var:
  447. """Divide two vars.
  448. Args:
  449. other: The other var to divide.
  450. Returns:
  451. A var representing the quotient.
  452. """
  453. return self.operation("/", other, flip=True)
  454. def __floordiv__(self, other: Var) -> Var:
  455. """Divide two vars.
  456. Args:
  457. other: The other var to divide.
  458. Returns:
  459. A var representing the quotient.
  460. """
  461. return self.operation("/", other, fn="Math.floor")
  462. def __mod__(self, other: Var) -> Var:
  463. """Get the remainder of two vars.
  464. Args:
  465. other: The other var to divide.
  466. Returns:
  467. A var representing the remainder.
  468. """
  469. return self.operation("%", other)
  470. def __rmod__(self, other: Var) -> Var:
  471. """Get the remainder of two vars.
  472. Args:
  473. other: The other var to divide.
  474. Returns:
  475. A var representing the remainder.
  476. """
  477. return self.operation("%", other, flip=True)
  478. def __and__(self, other: Var) -> Var:
  479. """Perform a logical and.
  480. Args:
  481. other: The other var to perform the logical and with.
  482. Returns:
  483. A var representing the logical and.
  484. """
  485. return self.operation("&&", other)
  486. def __rand__(self, other: Var) -> Var:
  487. """Perform a logical and.
  488. Args:
  489. other: The other var to perform the logical and with.
  490. Returns:
  491. A var representing the logical and.
  492. """
  493. return self.operation("&&", other, flip=True)
  494. def __or__(self, other: Var) -> Var:
  495. """Perform a logical or.
  496. Args:
  497. other: The other var to perform the logical or with.
  498. Returns:
  499. A var representing the logical or.
  500. """
  501. return self.operation("||", other)
  502. def __ror__(self, other: Var) -> Var:
  503. """Perform a logical or.
  504. Args:
  505. other: The other var to perform the logical or with.
  506. Returns:
  507. A var representing the logical or.
  508. """
  509. return self.operation("||", other, flip=True)
  510. def foreach(self, fn: Callable) -> Var:
  511. """Return a list of components. after doing a foreach on this var.
  512. Args:
  513. fn: The function to call on each component.
  514. Returns:
  515. A var representing foreach operation.
  516. """
  517. arg = BaseVar(
  518. name=get_unique_variable_name(),
  519. type_=self.type_,
  520. )
  521. return BaseVar(
  522. name=f"{self.full_name}.map(({arg.name}, i) => {fn(arg, key='i')})",
  523. type_=self.type_,
  524. )
  525. def to(self, type_: Type) -> Var:
  526. """Convert the type of the var.
  527. Args:
  528. type_: The type to convert to.
  529. Returns:
  530. The converted var.
  531. """
  532. return BaseVar(
  533. name=self.name,
  534. type_=type_,
  535. state=self.state,
  536. is_local=self.is_local,
  537. )
  538. @property
  539. def full_name(self) -> str:
  540. """Get the full name of the var.
  541. Returns:
  542. The full name of the var.
  543. """
  544. return self.name if self.state == "" else ".".join([self.state, self.name])
  545. def set_state(self, state: Type[State]) -> Any:
  546. """Set the state of the var.
  547. Args:
  548. state: The state to set.
  549. Returns:
  550. The var with the set state.
  551. """
  552. self.state = state.get_full_name()
  553. return self
  554. class BaseVar(Var, Base):
  555. """A base (non-computed) var of the app state."""
  556. # The name of the var.
  557. name: str
  558. # The type of the var.
  559. type_: Any
  560. # The name of the enclosing state.
  561. state: str = ""
  562. # Whether this is a local javascript variable.
  563. is_local: bool = False
  564. # Whether this var is a raw string.
  565. is_string: bool = False
  566. def __hash__(self) -> int:
  567. """Define a hash function for a var.
  568. Returns:
  569. The hash of the var.
  570. """
  571. return hash((self.name, str(self.type_)))
  572. def get_default_value(self) -> Any:
  573. """Get the default value of the var.
  574. Returns:
  575. The default value of the var.
  576. Raises:
  577. ImportError: If the var is a dataframe and pandas is not installed.
  578. """
  579. type_ = (
  580. self.type_.__origin__ if types.is_generic_alias(self.type_) else self.type_
  581. )
  582. if issubclass(type_, str):
  583. return ""
  584. if issubclass(type_, types.get_args(Union[int, float])):
  585. return 0
  586. if issubclass(type_, bool):
  587. return False
  588. if issubclass(type_, list):
  589. return []
  590. if issubclass(type_, dict):
  591. return {}
  592. if issubclass(type_, tuple):
  593. return ()
  594. if types.is_dataframe(type_):
  595. try:
  596. import pandas as pd
  597. return pd.DataFrame()
  598. except ImportError as e:
  599. raise ImportError(
  600. "Please install pandas to use dataframes in your app."
  601. ) from e
  602. return set() if issubclass(type_, set) else None
  603. def get_setter_name(self, include_state: bool = True) -> str:
  604. """Get the name of the var's generated setter function.
  605. Args:
  606. include_state: Whether to include the state name in the setter name.
  607. Returns:
  608. The name of the setter function.
  609. """
  610. setter = constants.SETTER_PREFIX + self.name
  611. if not include_state or self.state == "":
  612. return setter
  613. return ".".join((self.state, setter))
  614. def get_setter(self) -> Callable[[State, Any], None]:
  615. """Get the var's setter function.
  616. Returns:
  617. A function that that creates a setter for the var.
  618. """
  619. def setter(state: State, value: Any):
  620. """Get the setter for the var.
  621. Args:
  622. state: The state within which we add the setter function.
  623. value: The value to set.
  624. """
  625. setattr(state, self.name, value)
  626. setter.__qualname__ = self.get_setter_name()
  627. return setter
  628. class ComputedVar(Var, property):
  629. """A field with computed getters."""
  630. # Whether to track dependencies and cache computed values
  631. cache: bool = False
  632. @property
  633. def name(self) -> str:
  634. """Get the name of the var.
  635. Returns:
  636. The name of the var.
  637. """
  638. assert self.fget is not None, "Var must have a getter."
  639. return self.fget.__name__
  640. @property
  641. def cache_attr(self) -> str:
  642. """Get the attribute used to cache the value on the instance.
  643. Returns:
  644. An attribute name.
  645. """
  646. return f"__cached_{self.name}"
  647. def __get__(self, instance, owner):
  648. """Get the ComputedVar value.
  649. If the value is already cached on the instance, return the cached value.
  650. Args:
  651. instance: the instance of the class accessing this computed var.
  652. owner: the class that this descriptor is attached to.
  653. Returns:
  654. The value of the var for the given instance.
  655. """
  656. if instance is None or not self.cache:
  657. return super().__get__(instance, owner)
  658. # handle caching
  659. if not hasattr(instance, self.cache_attr):
  660. setattr(instance, self.cache_attr, super().__get__(instance, owner))
  661. return getattr(instance, self.cache_attr)
  662. def deps(
  663. self,
  664. objclass: Type,
  665. obj: Optional[FunctionType] = None,
  666. ) -> Set[str]:
  667. """Determine var dependencies of this ComputedVar.
  668. Save references to attributes accessed on "self". Recursively called
  669. when the function makes a method call on "self".
  670. Args:
  671. objclass: the class obj this ComputedVar is attached to.
  672. obj: the object to disassemble (defaults to the fget function).
  673. Returns:
  674. A set of variable names accessed by the given obj.
  675. """
  676. d = set()
  677. if obj is None:
  678. if self.fget is not None:
  679. obj = cast(FunctionType, self.fget)
  680. else:
  681. return set()
  682. with contextlib.suppress(AttributeError):
  683. # unbox functools.partial
  684. obj = cast(FunctionType, obj.func) # type: ignore
  685. with contextlib.suppress(AttributeError):
  686. # unbox EventHandler
  687. obj = cast(FunctionType, obj.fn) # type: ignore
  688. try:
  689. self_name = obj.__code__.co_varnames[0]
  690. except (AttributeError, IndexError):
  691. # cannot reference self if method takes no args
  692. return set()
  693. self_is_top_of_stack = False
  694. for instruction in dis.get_instructions(obj):
  695. if instruction.opname == "LOAD_FAST" and instruction.argval == self_name:
  696. self_is_top_of_stack = True
  697. continue
  698. if self_is_top_of_stack and instruction.opname == "LOAD_ATTR":
  699. d.add(instruction.argval)
  700. elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD":
  701. d.update(
  702. self.deps(
  703. objclass=objclass,
  704. obj=getattr(objclass, instruction.argval),
  705. )
  706. )
  707. self_is_top_of_stack = False
  708. return d
  709. def mark_dirty(self, instance) -> None:
  710. """Mark this ComputedVar as dirty.
  711. Args:
  712. instance: the state instance that needs to recompute the value.
  713. """
  714. with contextlib.suppress(AttributeError):
  715. delattr(instance, self.cache_attr)
  716. @property
  717. def type_(self):
  718. """Get the type of the var.
  719. Returns:
  720. The type of the var.
  721. """
  722. hints = get_type_hints(self.fget)
  723. if "return" in hints:
  724. return hints["return"]
  725. return Any
  726. def cached_var(fget: Callable[[Any], Any]) -> ComputedVar:
  727. """A field with computed getter that tracks other state dependencies.
  728. The cached_var will only be recalculated when other state vars that it
  729. depends on are modified.
  730. Args:
  731. fget: the function that calculates the variable value.
  732. Returns:
  733. ComputedVar that is recomputed when dependencies change.
  734. """
  735. cvar = ComputedVar(fget=fget)
  736. cvar.cache = True
  737. return cvar
  738. class ReflexList(list):
  739. """A custom list that reflex can detect its mutation."""
  740. def __init__(
  741. self,
  742. original_list: List,
  743. reassign_field: Callable = lambda _field_name: None,
  744. field_name: str = "",
  745. ):
  746. """Initialize ReflexList.
  747. Args:
  748. original_list (List): The original list
  749. reassign_field (Callable):
  750. The method in the parent state to reassign the field.
  751. Default to be a no-op function
  752. field_name (str): the name of field in the parent state
  753. """
  754. self._reassign_field = lambda: reassign_field(field_name)
  755. super().__init__(original_list)
  756. def append(self, *args, **kwargs):
  757. """Append.
  758. Args:
  759. args: The args passed.
  760. kwargs: The kwargs passed.
  761. """
  762. super().append(*args, **kwargs)
  763. self._reassign_field()
  764. def __setitem__(self, *args, **kwargs):
  765. """Set item.
  766. Args:
  767. args: The args passed.
  768. kwargs: The kwargs passed.
  769. """
  770. super().__setitem__(*args, **kwargs)
  771. self._reassign_field()
  772. def __delitem__(self, *args, **kwargs):
  773. """Delete item.
  774. Args:
  775. args: The args passed.
  776. kwargs: The kwargs passed.
  777. """
  778. super().__delitem__(*args, **kwargs)
  779. self._reassign_field()
  780. def clear(self, *args, **kwargs):
  781. """Remove all item from the list.
  782. Args:
  783. args: The args passed.
  784. kwargs: The kwargs passed.
  785. """
  786. super().clear(*args, **kwargs)
  787. self._reassign_field()
  788. def extend(self, *args, **kwargs):
  789. """Add all item of a list to the end of the list.
  790. Args:
  791. args: The args passed.
  792. kwargs: The kwargs passed.
  793. """
  794. super().extend(*args, **kwargs)
  795. self._reassign_field() if hasattr(self, "_reassign_field") else None
  796. def pop(self, *args, **kwargs):
  797. """Remove an element.
  798. Args:
  799. args: The args passed.
  800. kwargs: The kwargs passed.
  801. """
  802. super().pop(*args, **kwargs)
  803. self._reassign_field()
  804. def remove(self, *args, **kwargs):
  805. """Remove an element.
  806. Args:
  807. args: The args passed.
  808. kwargs: The kwargs passed.
  809. """
  810. super().remove(*args, **kwargs)
  811. self._reassign_field()
  812. class ReflexDict(dict):
  813. """A custom dict that reflex can detect its mutation."""
  814. def __init__(
  815. self,
  816. original_dict: Dict,
  817. reassign_field: Callable = lambda _field_name: None,
  818. field_name: str = "",
  819. ):
  820. """Initialize ReflexDict.
  821. Args:
  822. original_dict: The original dict
  823. reassign_field:
  824. The method in the parent state to reassign the field.
  825. Default to be a no-op function
  826. field_name: the name of field in the parent state
  827. """
  828. super().__init__(original_dict)
  829. self._reassign_field = lambda: reassign_field(field_name)
  830. def clear(self):
  831. """Remove all item from the list."""
  832. super().clear()
  833. self._reassign_field()
  834. def setdefault(self, *args, **kwargs):
  835. """Return value of key if or set default.
  836. Args:
  837. args: The args passed.
  838. kwargs: The kwargs passed.
  839. """
  840. super().setdefault(*args, **kwargs)
  841. self._reassign_field()
  842. def popitem(self):
  843. """Pop last item."""
  844. super().popitem()
  845. self._reassign_field()
  846. def pop(self, k, d=None):
  847. """Remove an element.
  848. Args:
  849. k: The args passed.
  850. d: The kwargs passed.
  851. """
  852. super().pop(k, d)
  853. self._reassign_field()
  854. def update(self, *args, **kwargs):
  855. """Update the dict with another dict.
  856. Args:
  857. args: The args passed.
  858. kwargs: The kwargs passed.
  859. """
  860. super().update(*args, **kwargs)
  861. self._reassign_field()
  862. def __setitem__(self, *args, **kwargs):
  863. """Set an item in the dict.
  864. Args:
  865. args: The args passed.
  866. kwargs: The kwargs passed.
  867. """
  868. super().__setitem__(*args, **kwargs)
  869. self._reassign_field() if hasattr(self, "_reassign_field") else None
  870. def __delitem__(self, *args, **kwargs):
  871. """Delete an item in the dict.
  872. Args:
  873. args: The args passed.
  874. kwargs: The kwargs passed.
  875. """
  876. super().__delitem__(*args, **kwargs)
  877. self._reassign_field()
  878. class ImportVar(Base):
  879. """An import var."""
  880. # The name of the import tag.
  881. tag: Optional[str]
  882. # whether the import is default or named.
  883. is_default: Optional[bool] = False
  884. # The tag alias.
  885. alias: Optional[str] = None
  886. @property
  887. def name(self) -> str:
  888. """The name of the import.
  889. Returns:
  890. The name(tag name with alias) of tag.
  891. """
  892. return self.tag if not self.alias else " as ".join([self.tag, self.alias]) # type: ignore
  893. def __hash__(self) -> int:
  894. """Define a hash function for the import var.
  895. Returns:
  896. The hash of the var.
  897. """
  898. return hash((self.tag, self.is_default, self.alias))
  899. def get_local_storage(key: Optional[Union[Var, str]] = None) -> BaseVar:
  900. """Provide a base var as payload to get local storage item(s).
  901. Args:
  902. key: Key to obtain value in the local storage.
  903. Returns:
  904. A BaseVar of the local storage method/function to call.
  905. Raises:
  906. TypeError: if the wrong key type is provided.
  907. """
  908. if key:
  909. if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str):
  910. type_ = type(key) if not isinstance(key, Var) else key.type_
  911. raise TypeError(
  912. f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead."
  913. )
  914. key = key.full_name if isinstance(key, Var) else format.wrap(key, "'")
  915. return BaseVar(name=f"localStorage.getItem({key})", type_=str)
  916. return BaseVar(name="getAllLocalStorageItems()", type_=Dict)