var.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. """Define a state var."""
  2. from __future__ import annotations
  3. import json
  4. from abc import ABC
  5. from typing import _GenericAlias # type: ignore
  6. from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type, Union
  7. from plotly.graph_objects import Figure
  8. from plotly.io import to_json
  9. from pydantic.fields import ModelField
  10. from pynecone import constants, utils
  11. from pynecone.base import Base
  12. if TYPE_CHECKING:
  13. from pynecone.state import State
  14. class Var(ABC):
  15. """An abstract var."""
  16. # The name of the var.
  17. name: str
  18. # The type of the var.
  19. type_: Type
  20. # The name of the enclosing state.
  21. state: str = ""
  22. # Whether this is a local javascript variable.
  23. is_local: bool = False
  24. # Whether the var is a string literal.
  25. is_string: bool = False
  26. @classmethod
  27. def create(
  28. cls, value: Any, is_local: bool = True, is_string: bool = False
  29. ) -> Optional[Var]:
  30. """Create a var from a value.
  31. Args:
  32. value: The value to create the var from.
  33. is_local: Whether the var is local.
  34. is_string: Whether the var is a string literal.
  35. Returns:
  36. The var.
  37. """
  38. # Check for none values.
  39. if value is None:
  40. return None
  41. # If the value is already a var, do nothing.
  42. if isinstance(value, Var):
  43. return value
  44. type_ = type(value)
  45. # Special case for plotly figures.
  46. if isinstance(value, Figure):
  47. value = json.loads(to_json(value))["data"]
  48. type_ = Figure
  49. name = value if isinstance(value, str) else json.dumps(value)
  50. return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
  51. @classmethod
  52. def __class_getitem__(cls, type_: str) -> _GenericAlias:
  53. """Get a typed var.
  54. Args:
  55. type_: The type of the var.
  56. Returns:
  57. The var class item.
  58. """
  59. return _GenericAlias(cls, type_)
  60. def equals(self, other: Var) -> bool:
  61. """Check if two vars are equal.
  62. Args:
  63. other: The other var to compare.
  64. Returns:
  65. Whether the vars are equal.
  66. """
  67. return (
  68. self.name == other.name
  69. and self.type_ == other.type_
  70. and self.state == other.state
  71. and self.is_local == other.is_local
  72. )
  73. def to_string(self) -> Var:
  74. """Convert a var to a string.
  75. Returns:
  76. The stringified var.
  77. """
  78. return self.operation(fn="JSON.stringify")
  79. def __hash__(self) -> int:
  80. """Define a hash function for a var.
  81. Returns:
  82. The hash of the var.
  83. """
  84. return hash((self.name, str(self.type_)))
  85. def __str__(self) -> str:
  86. """Wrap the var so it can be used in templates.
  87. Returns:
  88. The wrapped var, i.e. {state.var}.
  89. """
  90. out = self.full_name if self.is_local else utils.wrap(self.full_name, "{")
  91. if self.is_string:
  92. out = utils.format_string(out)
  93. return out
  94. def __getitem__(self, i: Any) -> Var:
  95. """Index into a var.
  96. Args:
  97. i: The index to index into.
  98. Returns:
  99. The indexed var.
  100. Raises:
  101. TypeError: If the var is not indexable.
  102. """
  103. # Indexing is only supported for lists, dicts, and dataframes.
  104. if not (
  105. utils._issubclass(self.type_, Union[List, Dict])
  106. or utils.is_dataframe(self.type_)
  107. ):
  108. raise TypeError(
  109. f"Var {self.name} of type {self.type_} does not support indexing."
  110. )
  111. # The type of the indexed var.
  112. type_ = Any
  113. # Convert any vars to local vars.
  114. if isinstance(i, Var):
  115. i = BaseVar(name=i.name, type_=i.type_, state=i.state, is_local=True)
  116. # Handle list indexing.
  117. if utils._issubclass(self.type_, List):
  118. # List indices must be ints, slices, or vars.
  119. if not isinstance(i, utils.get_args(Union[int, slice, Var])):
  120. raise TypeError("Index must be an integer.")
  121. # Handle slices first.
  122. if isinstance(i, slice):
  123. # Get the start and stop indices.
  124. start = i.start or 0
  125. stop = i.stop or "undefined"
  126. # Use the slice function.
  127. return BaseVar(
  128. name=f"{self.name}.slice({start}, {stop})",
  129. type_=self.type_,
  130. state=self.state,
  131. )
  132. # Get the type of the indexed var.
  133. if utils.is_generic_alias(self.type_):
  134. type_ = utils.get_args(self.type_)[0]
  135. else:
  136. type_ = Any
  137. # Use `at` to support negative indices.
  138. return BaseVar(
  139. name=f"{self.name}.at({i})",
  140. type_=type_,
  141. state=self.state,
  142. )
  143. # Dictionary / dataframe indexing.
  144. # Get the type of the indexed var.
  145. if isinstance(i, str):
  146. i = utils.wrap(i, '"')
  147. if utils.is_generic_alias(self.type_):
  148. type_ = utils.get_args(self.type_)[1]
  149. else:
  150. type_ = Any
  151. # Use normal indexing here.
  152. return BaseVar(
  153. name=f"{self.name}[{i}]",
  154. type_=type_,
  155. state=self.state,
  156. )
  157. def __getattribute__(self, name: str) -> Var:
  158. """Get a var attribute.
  159. Args:
  160. name: The name of the attribute.
  161. Returns:
  162. The var attribute.
  163. Raises:
  164. Exception: If the attribute is not found.
  165. """
  166. try:
  167. return super().__getattribute__(name)
  168. except Exception as e:
  169. # Check if the attribute is one of the class fields.
  170. if (
  171. not name.startswith("_")
  172. and hasattr(self.type_, "__fields__")
  173. and name in self.type_.__fields__
  174. ):
  175. type_ = self.type_.__fields__[name].outer_type_
  176. if isinstance(type_, ModelField):
  177. type_ = type_.type_
  178. return BaseVar(
  179. name=f"{self.name}.{name}",
  180. type_=type_,
  181. state=self.state,
  182. )
  183. raise e
  184. def operation(
  185. self,
  186. op: str = "",
  187. other: Optional[Var] = None,
  188. type_: Optional[Type] = None,
  189. flip: bool = False,
  190. fn: Optional[str] = None,
  191. ) -> Var:
  192. """Perform an operation on a var.
  193. Args:
  194. op: The operation to perform.
  195. other: The other var to perform the operation on.
  196. type_: The type of the operation result.
  197. flip: Whether to flip the order of the operation.
  198. fn: A function to apply to the operation.
  199. Returns:
  200. The operation result.
  201. """
  202. # Wrap strings in quotes.
  203. if isinstance(other, str):
  204. other = Var.create(json.dumps(other))
  205. else:
  206. other = Var.create(other)
  207. if type_ is None:
  208. type_ = self.type_
  209. if other is None:
  210. name = f"{op}{self.full_name}"
  211. else:
  212. props = (other, self) if flip else (self, other)
  213. name = f"{props[0].full_name} {op} {props[1].full_name}"
  214. if fn is None:
  215. name = utils.wrap(name, "(")
  216. if fn is not None:
  217. name = f"{fn}({name})"
  218. return BaseVar(
  219. name=name,
  220. type_=type_,
  221. )
  222. def compare(self, op: str, other: Var) -> Var:
  223. """Compare two vars with inequalities.
  224. Args:
  225. op: The comparison operator.
  226. other: The other var to compare with.
  227. Returns:
  228. The comparison result.
  229. """
  230. return self.operation(op, other, bool)
  231. def __invert__(self) -> Var:
  232. """Invert a var.
  233. Returns:
  234. The inverted var.
  235. """
  236. return self.operation("!", type_=bool)
  237. def __neg__(self) -> Var:
  238. """Negate a var.
  239. Returns:
  240. The negated var.
  241. """
  242. return self.operation(fn="-")
  243. def __abs__(self) -> Var:
  244. """Get the absolute value of a var.
  245. Returns:
  246. A var with the absolute value.
  247. """
  248. return self.operation(fn="Math.abs")
  249. def length(self) -> Var:
  250. """Get the length of a list var.
  251. Returns:
  252. A var with the absolute value.
  253. Raises:
  254. TypeError: If the var is not a list.
  255. """
  256. if not utils._issubclass(self.type_, List):
  257. raise TypeError(f"Cannot get length of non-list var {self}.")
  258. return BaseVar(
  259. name=f"{self.full_name}.length",
  260. type_=int,
  261. )
  262. def __eq__(self, other: Var) -> Var:
  263. """Perform an equality comparison.
  264. Args:
  265. other: The other var to compare with.
  266. Returns:
  267. A var representing the equality comparison.
  268. """
  269. return self.compare("==", other)
  270. def __ne__(self, other: Var) -> Var:
  271. """Perform an inequality comparison.
  272. Args:
  273. other: The other var to compare with.
  274. Returns:
  275. A var representing the inequality comparison.
  276. """
  277. return self.compare("!=", other)
  278. def __gt__(self, other: Var) -> Var:
  279. """Perform a greater than comparison.
  280. Args:
  281. other: The other var to compare with.
  282. Returns:
  283. A var representing the greater than comparison.
  284. """
  285. return self.compare(">", other)
  286. def __ge__(self, other: Var) -> Var:
  287. """Perform a greater than or equal to comparison.
  288. Args:
  289. other: The other var to compare with.
  290. Returns:
  291. A var representing the greater than or equal to comparison.
  292. """
  293. return self.compare(">=", other)
  294. def __lt__(self, other: Var) -> Var:
  295. """Perform a less than comparison.
  296. Args:
  297. other: The other var to compare with.
  298. Returns:
  299. A var representing the less than comparison.
  300. """
  301. return self.compare("<", other)
  302. def __le__(self, other: Var) -> Var:
  303. """Perform a less than or equal to comparison.
  304. Args:
  305. other: The other var to compare with.
  306. Returns:
  307. A var representing the less than or equal to comparison.
  308. """
  309. return self.compare("<=", other)
  310. def __add__(self, other: Var) -> Var:
  311. """Add two vars.
  312. Args:
  313. other: The other var to add.
  314. Returns:
  315. A var representing the sum.
  316. """
  317. return self.operation("+", other)
  318. def __radd__(self, other: Var) -> Var:
  319. """Add two vars.
  320. Args:
  321. other: The other var to add.
  322. Returns:
  323. A var representing the sum.
  324. """
  325. return self.operation("+", other, flip=True)
  326. def __sub__(self, other: Var) -> Var:
  327. """Subtract two vars.
  328. Args:
  329. other: The other var to subtract.
  330. Returns:
  331. A var representing the difference.
  332. """
  333. return self.operation("-", other)
  334. def __rsub__(self, other: Var) -> Var:
  335. """Subtract two vars.
  336. Args:
  337. other: The other var to subtract.
  338. Returns:
  339. A var representing the difference.
  340. """
  341. return self.operation("-", other, flip=True)
  342. def __mul__(self, other: Var) -> Var:
  343. """Multiply two vars.
  344. Args:
  345. other: The other var to multiply.
  346. Returns:
  347. A var representing the product.
  348. """
  349. return self.operation("*", other)
  350. def __rmul__(self, other: Var) -> Var:
  351. """Multiply two vars.
  352. Args:
  353. other: The other var to multiply.
  354. Returns:
  355. A var representing the product.
  356. """
  357. return self.operation("*", other, flip=True)
  358. def __pow__(self, other: Var) -> Var:
  359. """Raise a var to a power.
  360. Args:
  361. other: The power to raise to.
  362. Returns:
  363. A var representing the power.
  364. """
  365. return self.operation(",", other, fn="Math.pow")
  366. def __rpow__(self, other: Var) -> Var:
  367. """Raise a var to a power.
  368. Args:
  369. other: The power to raise to.
  370. Returns:
  371. A var representing the power.
  372. """
  373. return self.operation(",", other, flip=True, fn="Math.pow")
  374. def __truediv__(self, other: Var) -> Var:
  375. """Divide two vars.
  376. Args:
  377. other: The other var to divide.
  378. Returns:
  379. A var representing the quotient.
  380. """
  381. return self.operation("/", other)
  382. def __rtruediv__(self, other: Var) -> Var:
  383. """Divide two vars.
  384. Args:
  385. other: The other var to divide.
  386. Returns:
  387. A var representing the quotient.
  388. """
  389. return self.operation("/", other, flip=True)
  390. def __floordiv__(self, other: Var) -> Var:
  391. """Divide two vars.
  392. Args:
  393. other: The other var to divide.
  394. Returns:
  395. A var representing the quotient.
  396. """
  397. return self.operation("/", other, fn="Math.floor")
  398. def __mod__(self, other: Var) -> Var:
  399. """Get the remainder of two vars.
  400. Args:
  401. other: The other var to divide.
  402. Returns:
  403. A var representing the remainder.
  404. """
  405. return self.operation("%", other)
  406. def __rmod__(self, other: Var) -> Var:
  407. """Get the remainder of two vars.
  408. Args:
  409. other: The other var to divide.
  410. Returns:
  411. A var representing the remainder.
  412. """
  413. return self.operation("%", other, flip=True)
  414. def __and__(self, other: Var) -> Var:
  415. """Perform a logical and.
  416. Args:
  417. other: The other var to perform the logical and with.
  418. Returns:
  419. A var representing the logical and.
  420. """
  421. return self.operation("&&", other)
  422. def __rand__(self, other: Var) -> Var:
  423. """Perform a logical and.
  424. Args:
  425. other: The other var to perform the logical and with.
  426. Returns:
  427. A var representing the logical and.
  428. """
  429. return self.operation("&&", other, flip=True)
  430. def __or__(self, other: Var) -> Var:
  431. """Perform a logical or.
  432. Args:
  433. other: The other var to perform the logical or with.
  434. Returns:
  435. A var representing the logical or.
  436. """
  437. return self.operation("||", other)
  438. def __ror__(self, other: Var) -> Var:
  439. """Perform a logical or.
  440. Args:
  441. other: The other var to perform the logical or with.
  442. Returns:
  443. A var representing the logical or.
  444. """
  445. return self.operation("||", other, flip=True)
  446. def foreach(self, fn: Callable) -> Var:
  447. """Return a list of components. after doing a foreach on this var.
  448. Args:
  449. fn: The function to call on each component.
  450. Returns:
  451. A var representing foreach operation.
  452. """
  453. arg = BaseVar(
  454. name=utils.get_unique_variable_name(),
  455. type_=self.type_,
  456. )
  457. return BaseVar(
  458. name=f"{self.full_name}.map(({arg.name}, i) => {fn(arg, key='i')})",
  459. type_=self.type_,
  460. )
  461. def to(self, type_: Type) -> Var:
  462. """Convert the type of the var.
  463. Args:
  464. type_: The type to convert to.
  465. Returns:
  466. The converted var.
  467. """
  468. return BaseVar(
  469. name=self.name,
  470. type_=type_,
  471. state=self.state,
  472. is_local=self.is_local,
  473. )
  474. @property
  475. def full_name(self) -> str:
  476. """Get the full name of the var.
  477. Returns:
  478. The full name of the var.
  479. """
  480. return self.name if self.state == "" else ".".join([self.state, self.name])
  481. def set_state(self, state: Type[State]) -> Any:
  482. """Set the state of the var.
  483. Args:
  484. state: The state to set.
  485. Returns:
  486. The var with the set state.
  487. """
  488. self.state = state.get_full_name()
  489. return self
  490. class BaseVar(Var, Base):
  491. """A base (non-computed) var of the app state."""
  492. # The name of the var.
  493. name: str
  494. # The type of the var.
  495. type_: Any
  496. # The name of the enclosing state.
  497. state: str = ""
  498. # Whether this is a local javascript variable.
  499. is_local: bool = False
  500. # Whether this var is a raw string.
  501. is_string: bool = False
  502. def __hash__(self) -> int:
  503. """Define a hash function for a var.
  504. Returns:
  505. The hash of the var.
  506. """
  507. return hash((self.name, str(self.type_)))
  508. def get_default_value(self) -> Any:
  509. """Get the default value of the var.
  510. Returns:
  511. The default value of the var.
  512. """
  513. if utils.is_generic_alias(self.type_):
  514. type_ = self.type_.__origin__
  515. else:
  516. type_ = self.type_
  517. if issubclass(type_, str):
  518. return ""
  519. if issubclass(type_, utils.get_args(Union[int, float])):
  520. return 0
  521. if issubclass(type_, bool):
  522. return False
  523. if issubclass(type_, list):
  524. return []
  525. if issubclass(type_, dict):
  526. return {}
  527. if issubclass(type_, tuple):
  528. return ()
  529. return set() if issubclass(type_, set) else None
  530. def get_setter_name(self, include_state: bool = True) -> str:
  531. """Get the name of the var's generated setter function.
  532. Args:
  533. include_state: Whether to include the state name in the setter name.
  534. Returns:
  535. The name of the setter function.
  536. """
  537. setter = constants.SETTER_PREFIX + self.name
  538. if not include_state or self.state == "":
  539. return setter
  540. return ".".join((self.state, setter))
  541. def get_setter(self) -> Callable[[State, Any], None]:
  542. """Get the var's setter function.
  543. Returns:
  544. A function that that creates a setter for the var.
  545. """
  546. def setter(state: State, value: Any):
  547. """Get the setter for the var.
  548. Args:
  549. state: The state within which we add the setter function.
  550. value: The value to set.
  551. """
  552. setattr(state, self.name, value)
  553. setter.__qualname__ = self.get_setter_name()
  554. return setter
  555. def json(self) -> str:
  556. """Convert the object to a json string.
  557. Returns:
  558. The object as a json string.
  559. """
  560. return self.__config__.json_dumps(self.dict())
  561. class ComputedVar(property, Var):
  562. """A field with computed getters."""
  563. @property
  564. def name(self) -> str:
  565. """Get the name of the var.
  566. Returns:
  567. The name of the var.
  568. """
  569. assert self.fget is not None, "Var must have a getter."
  570. return self.fget.__name__
  571. @property
  572. def type_(self):
  573. """Get the type of the var.
  574. Returns:
  575. The type of the var.
  576. """
  577. if "return" in self.fget.__annotations__:
  578. return self.fget.__annotations__["return"]
  579. return Any