number.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. """Immutable number vars."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import decimal
  5. import json
  6. import math
  7. from collections.abc import Callable
  8. from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, overload
  9. from typing_extensions import TypeVar as TypeVarExt
  10. from reflex.constants.base import Dirs
  11. from reflex.utils.exceptions import (
  12. PrimitiveUnserializableToJSONError,
  13. VarTypeError,
  14. VarValueError,
  15. )
  16. from reflex.utils.imports import ImportDict, ImportVar
  17. from reflex.utils.types import safe_issubclass
  18. from .base import (
  19. CustomVarOperationReturn,
  20. LiteralVar,
  21. Var,
  22. VarData,
  23. unionize,
  24. var_operation,
  25. var_operation_return,
  26. )
  27. NUMBER_T = TypeVarExt(
  28. "NUMBER_T",
  29. bound=(int | float | decimal.Decimal),
  30. default=(int | float | decimal.Decimal),
  31. covariant=True,
  32. )
  33. if TYPE_CHECKING:
  34. from .sequence import ArrayVar
  35. def raise_unsupported_operand_types(
  36. operator: str, operands_types: tuple[type, ...]
  37. ) -> NoReturn:
  38. """Raise an unsupported operand types error.
  39. Args:
  40. operator: The operator.
  41. operands_types: The types of the operands.
  42. Raises:
  43. VarTypeError: The operand types are unsupported.
  44. """
  45. msg = f"Unsupported Operand type(s) for {operator}: {', '.join(t.__name__ for t in operands_types)}"
  46. raise VarTypeError(msg)
  47. class NumberVar(Var[NUMBER_T], python_types=(int, float, decimal.Decimal)):
  48. """Base class for immutable number vars."""
  49. def __add__(self, other: number_types) -> NumberVar:
  50. """Add two numbers.
  51. Args:
  52. other: The other number.
  53. Returns:
  54. The number addition operation.
  55. """
  56. if not isinstance(other, NUMBER_TYPES):
  57. raise_unsupported_operand_types("+", (type(self), type(other)))
  58. return number_add_operation(self, +other)
  59. def __radd__(self, other: number_types) -> NumberVar:
  60. """Add two numbers.
  61. Args:
  62. other: The other number.
  63. Returns:
  64. The number addition operation.
  65. """
  66. if not isinstance(other, NUMBER_TYPES):
  67. raise_unsupported_operand_types("+", (type(other), type(self)))
  68. return number_add_operation(+other, self)
  69. def __sub__(self, other: number_types) -> NumberVar:
  70. """Subtract two numbers.
  71. Args:
  72. other: The other number.
  73. Returns:
  74. The number subtraction operation.
  75. """
  76. if not isinstance(other, NUMBER_TYPES):
  77. raise_unsupported_operand_types("-", (type(self), type(other)))
  78. return number_subtract_operation(self, +other)
  79. def __rsub__(self, other: number_types) -> NumberVar:
  80. """Subtract two numbers.
  81. Args:
  82. other: The other number.
  83. Returns:
  84. The number subtraction operation.
  85. """
  86. if not isinstance(other, NUMBER_TYPES):
  87. raise_unsupported_operand_types("-", (type(other), type(self)))
  88. return number_subtract_operation(+other, self)
  89. def __abs__(self):
  90. """Get the absolute value of the number.
  91. Returns:
  92. The number absolute operation.
  93. """
  94. return number_abs_operation(self)
  95. @overload
  96. def __mul__(self, other: number_types | boolean_types) -> NumberVar: ...
  97. @overload
  98. def __mul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  99. def __mul__(self, other: Any):
  100. """Multiply two numbers.
  101. Args:
  102. other: The other number.
  103. Returns:
  104. The number multiplication operation.
  105. """
  106. from .sequence import ArrayVar, LiteralArrayVar
  107. if isinstance(other, (list, tuple, ArrayVar)):
  108. if isinstance(other, ArrayVar):
  109. return other * self
  110. return LiteralArrayVar.create(other) * self
  111. if not isinstance(other, NUMBER_TYPES):
  112. raise_unsupported_operand_types("*", (type(self), type(other)))
  113. return number_multiply_operation(self, +other)
  114. @overload
  115. def __rmul__(self, other: number_types | boolean_types) -> NumberVar: ...
  116. @overload
  117. def __rmul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  118. def __rmul__(self, other: Any):
  119. """Multiply two numbers.
  120. Args:
  121. other: The other number.
  122. Returns:
  123. The number multiplication operation.
  124. """
  125. from .sequence import ArrayVar, LiteralArrayVar
  126. if isinstance(other, (list, tuple, ArrayVar)):
  127. if isinstance(other, ArrayVar):
  128. return other * self
  129. return LiteralArrayVar.create(other) * self
  130. if not isinstance(other, NUMBER_TYPES):
  131. raise_unsupported_operand_types("*", (type(other), type(self)))
  132. return number_multiply_operation(+other, self)
  133. def __truediv__(self, other: number_types) -> NumberVar:
  134. """Divide two numbers.
  135. Args:
  136. other: The other number.
  137. Returns:
  138. The number true division operation.
  139. """
  140. if not isinstance(other, NUMBER_TYPES):
  141. raise_unsupported_operand_types("/", (type(self), type(other)))
  142. return number_true_division_operation(self, +other)
  143. def __rtruediv__(self, other: number_types) -> NumberVar:
  144. """Divide two numbers.
  145. Args:
  146. other: The other number.
  147. Returns:
  148. The number true division operation.
  149. """
  150. if not isinstance(other, NUMBER_TYPES):
  151. raise_unsupported_operand_types("/", (type(other), type(self)))
  152. return number_true_division_operation(+other, self)
  153. def __floordiv__(self, other: number_types) -> NumberVar:
  154. """Floor divide two numbers.
  155. Args:
  156. other: The other number.
  157. Returns:
  158. The number floor division operation.
  159. """
  160. if not isinstance(other, NUMBER_TYPES):
  161. raise_unsupported_operand_types("//", (type(self), type(other)))
  162. return number_floor_division_operation(self, +other)
  163. def __rfloordiv__(self, other: number_types) -> NumberVar:
  164. """Floor divide two numbers.
  165. Args:
  166. other: The other number.
  167. Returns:
  168. The number floor division operation.
  169. """
  170. if not isinstance(other, NUMBER_TYPES):
  171. raise_unsupported_operand_types("//", (type(other), type(self)))
  172. return number_floor_division_operation(+other, self)
  173. def __mod__(self, other: number_types) -> NumberVar:
  174. """Modulo two numbers.
  175. Args:
  176. other: The other number.
  177. Returns:
  178. The number modulo operation.
  179. """
  180. if not isinstance(other, NUMBER_TYPES):
  181. raise_unsupported_operand_types("%", (type(self), type(other)))
  182. return number_modulo_operation(self, +other)
  183. def __rmod__(self, other: number_types) -> NumberVar:
  184. """Modulo two numbers.
  185. Args:
  186. other: The other number.
  187. Returns:
  188. The number modulo operation.
  189. """
  190. if not isinstance(other, NUMBER_TYPES):
  191. raise_unsupported_operand_types("%", (type(other), type(self)))
  192. return number_modulo_operation(+other, self)
  193. def __pow__(self, other: number_types) -> NumberVar:
  194. """Exponentiate two numbers.
  195. Args:
  196. other: The other number.
  197. Returns:
  198. The number exponent operation.
  199. """
  200. if not isinstance(other, NUMBER_TYPES):
  201. raise_unsupported_operand_types("**", (type(self), type(other)))
  202. return number_exponent_operation(self, +other)
  203. def __rpow__(self, other: number_types) -> NumberVar:
  204. """Exponentiate two numbers.
  205. Args:
  206. other: The other number.
  207. Returns:
  208. The number exponent operation.
  209. """
  210. if not isinstance(other, NUMBER_TYPES):
  211. raise_unsupported_operand_types("**", (type(other), type(self)))
  212. return number_exponent_operation(+other, self)
  213. def __neg__(self) -> NumberVar:
  214. """Negate the number.
  215. Returns:
  216. The number negation operation.
  217. """
  218. return number_negate_operation(self) # pyright: ignore [reportReturnType]
  219. def __invert__(self):
  220. """Boolean NOT the number.
  221. Returns:
  222. The boolean NOT operation.
  223. """
  224. return boolean_not_operation(self.bool())
  225. def __pos__(self) -> NumberVar:
  226. """Positive the number.
  227. Returns:
  228. The number.
  229. """
  230. return self
  231. def __round__(self, ndigits: int | NumberVar = 0) -> NumberVar:
  232. """Round the number.
  233. Args:
  234. ndigits: The number of digits to round.
  235. Returns:
  236. The number round operation.
  237. """
  238. if not isinstance(ndigits, NUMBER_TYPES):
  239. raise_unsupported_operand_types("round", (type(self), type(ndigits)))
  240. return number_round_operation(self, +ndigits)
  241. def __ceil__(self):
  242. """Ceil the number.
  243. Returns:
  244. The number ceil operation.
  245. """
  246. return number_ceil_operation(self)
  247. def __floor__(self):
  248. """Floor the number.
  249. Returns:
  250. The number floor operation.
  251. """
  252. return number_floor_operation(self)
  253. def __trunc__(self):
  254. """Trunc the number.
  255. Returns:
  256. The number trunc operation.
  257. """
  258. return number_trunc_operation(self)
  259. def __lt__(self, other: number_types) -> BooleanVar:
  260. """Less than comparison.
  261. Args:
  262. other: The other number.
  263. Returns:
  264. The result of the comparison.
  265. """
  266. if not isinstance(other, NUMBER_TYPES):
  267. raise_unsupported_operand_types("<", (type(self), type(other)))
  268. return less_than_operation(+self, +other)
  269. def __le__(self, other: number_types) -> BooleanVar:
  270. """Less than or equal comparison.
  271. Args:
  272. other: The other number.
  273. Returns:
  274. The result of the comparison.
  275. """
  276. if not isinstance(other, NUMBER_TYPES):
  277. raise_unsupported_operand_types("<=", (type(self), type(other)))
  278. return less_than_or_equal_operation(+self, +other)
  279. def __eq__(self, other: Any):
  280. """Equal comparison.
  281. Args:
  282. other: The other number.
  283. Returns:
  284. The result of the comparison.
  285. """
  286. if isinstance(other, NUMBER_TYPES):
  287. return equal_operation(+self, +other)
  288. return equal_operation(self, other)
  289. def __ne__(self, other: Any):
  290. """Not equal comparison.
  291. Args:
  292. other: The other number.
  293. Returns:
  294. The result of the comparison.
  295. """
  296. if isinstance(other, NUMBER_TYPES):
  297. return not_equal_operation(+self, +other)
  298. return not_equal_operation(self, other)
  299. def __gt__(self, other: number_types) -> BooleanVar:
  300. """Greater than comparison.
  301. Args:
  302. other: The other number.
  303. Returns:
  304. The result of the comparison.
  305. """
  306. if not isinstance(other, NUMBER_TYPES):
  307. raise_unsupported_operand_types(">", (type(self), type(other)))
  308. return greater_than_operation(+self, +other)
  309. def __ge__(self, other: number_types) -> BooleanVar:
  310. """Greater than or equal comparison.
  311. Args:
  312. other: The other number.
  313. Returns:
  314. The result of the comparison.
  315. """
  316. if not isinstance(other, NUMBER_TYPES):
  317. raise_unsupported_operand_types(">=", (type(self), type(other)))
  318. return greater_than_or_equal_operation(+self, +other)
  319. def _is_strict_float(self) -> bool:
  320. """Check if the number is a float.
  321. Returns:
  322. bool: True if the number is a float.
  323. """
  324. return safe_issubclass(self._var_type, float)
  325. def _is_strict_int(self) -> bool:
  326. """Check if the number is an int.
  327. Returns:
  328. bool: True if the number is an int.
  329. """
  330. return safe_issubclass(self._var_type, int)
  331. def __format__(self, format_spec: str) -> str:
  332. """Format the number.
  333. Args:
  334. format_spec: The format specifier.
  335. Returns:
  336. The formatted number.
  337. Raises:
  338. VarValueError: If the format specifier is not supported.
  339. """
  340. from .sequence import (
  341. get_decimal_string_operation,
  342. get_decimal_string_separator_operation,
  343. )
  344. separator = ""
  345. if format_spec and format_spec[:1] == ",":
  346. separator = ","
  347. format_spec = format_spec[1:]
  348. elif format_spec and format_spec[:1] == "_":
  349. separator = "_"
  350. format_spec = format_spec[1:]
  351. if (
  352. format_spec
  353. and format_spec[-1] == "f"
  354. and format_spec[0] == "."
  355. and format_spec[1:-1].isdigit()
  356. ):
  357. how_many_decimals = int(format_spec[1:-1])
  358. return f"{get_decimal_string_operation(self, Var.create(how_many_decimals), Var.create(separator))}"
  359. if not format_spec and separator:
  360. return (
  361. f"{get_decimal_string_separator_operation(self, Var.create(separator))}"
  362. )
  363. if format_spec:
  364. msg = (
  365. f"Unknown format code '{format_spec}' for object of type 'NumberVar'. It is only supported to use ',', '_', and '.f' for float numbers."
  366. "If possible, use computed variables instead: https://reflex.dev/docs/vars/computed-vars/"
  367. )
  368. raise VarValueError(msg)
  369. return super().__format__(format_spec)
  370. def binary_number_operation(
  371. func: Callable[[NumberVar, NumberVar], str],
  372. ) -> Callable[[number_types, number_types], NumberVar]:
  373. """Decorator to create a binary number operation.
  374. Args:
  375. func: The binary number operation function.
  376. Returns:
  377. The binary number operation.
  378. """
  379. @var_operation
  380. def operation(lhs: NumberVar, rhs: NumberVar):
  381. return var_operation_return(
  382. js_expression=func(lhs, rhs),
  383. var_type=unionize(lhs._var_type, rhs._var_type),
  384. )
  385. def wrapper(lhs: number_types, rhs: number_types) -> NumberVar:
  386. """Create the binary number operation.
  387. Args:
  388. lhs: The first number.
  389. rhs: The second number.
  390. Returns:
  391. The binary number operation.
  392. """
  393. return operation(lhs, rhs) # pyright: ignore [reportReturnType, reportArgumentType]
  394. return wrapper
  395. @binary_number_operation
  396. def number_add_operation(lhs: NumberVar, rhs: NumberVar):
  397. """Add two numbers.
  398. Args:
  399. lhs: The first number.
  400. rhs: The second number.
  401. Returns:
  402. The number addition operation.
  403. """
  404. return f"({lhs} + {rhs})"
  405. @binary_number_operation
  406. def number_subtract_operation(lhs: NumberVar, rhs: NumberVar):
  407. """Subtract two numbers.
  408. Args:
  409. lhs: The first number.
  410. rhs: The second number.
  411. Returns:
  412. The number subtraction operation.
  413. """
  414. return f"({lhs} - {rhs})"
  415. @var_operation
  416. def number_abs_operation(value: NumberVar):
  417. """Get the absolute value of the number.
  418. Args:
  419. value: The number.
  420. Returns:
  421. The number absolute operation.
  422. """
  423. return var_operation_return(
  424. js_expression=f"Math.abs({value})", var_type=value._var_type
  425. )
  426. @binary_number_operation
  427. def number_multiply_operation(lhs: NumberVar, rhs: NumberVar):
  428. """Multiply two numbers.
  429. Args:
  430. lhs: The first number.
  431. rhs: The second number.
  432. Returns:
  433. The number multiplication operation.
  434. """
  435. return f"({lhs} * {rhs})"
  436. @var_operation
  437. def number_negate_operation(
  438. value: NumberVar[NUMBER_T],
  439. ) -> CustomVarOperationReturn[NUMBER_T]:
  440. """Negate the number.
  441. Args:
  442. value: The number.
  443. Returns:
  444. The number negation operation.
  445. """
  446. return var_operation_return(js_expression=f"-({value})", var_type=value._var_type)
  447. @binary_number_operation
  448. def number_true_division_operation(lhs: NumberVar, rhs: NumberVar):
  449. """Divide two numbers.
  450. Args:
  451. lhs: The first number.
  452. rhs: The second number.
  453. Returns:
  454. The number true division operation.
  455. """
  456. return f"({lhs} / {rhs})"
  457. @binary_number_operation
  458. def number_floor_division_operation(lhs: NumberVar, rhs: NumberVar):
  459. """Floor divide two numbers.
  460. Args:
  461. lhs: The first number.
  462. rhs: The second number.
  463. Returns:
  464. The number floor division operation.
  465. """
  466. return f"Math.floor({lhs} / {rhs})"
  467. @binary_number_operation
  468. def number_modulo_operation(lhs: NumberVar, rhs: NumberVar):
  469. """Modulo two numbers.
  470. Args:
  471. lhs: The first number.
  472. rhs: The second number.
  473. Returns:
  474. The number modulo operation.
  475. """
  476. return f"({lhs} % {rhs})"
  477. @binary_number_operation
  478. def number_exponent_operation(lhs: NumberVar, rhs: NumberVar):
  479. """Exponentiate two numbers.
  480. Args:
  481. lhs: The first number.
  482. rhs: The second number.
  483. Returns:
  484. The number exponent operation.
  485. """
  486. return f"({lhs} ** {rhs})"
  487. @var_operation
  488. def number_round_operation(value: NumberVar, ndigits: NumberVar | int):
  489. """Round the number.
  490. Args:
  491. value: The number.
  492. ndigits: The number of digits.
  493. Returns:
  494. The number round operation.
  495. """
  496. if (isinstance(ndigits, LiteralNumberVar) and ndigits._var_value == 0) or (
  497. isinstance(ndigits, int) and ndigits == 0
  498. ):
  499. return var_operation_return(js_expression=f"Math.round({value})", var_type=int)
  500. return var_operation_return(
  501. js_expression=f"(+{value}.toFixed({ndigits}))", var_type=float
  502. )
  503. @var_operation
  504. def number_ceil_operation(value: NumberVar):
  505. """Ceil the number.
  506. Args:
  507. value: The number.
  508. Returns:
  509. The number ceil operation.
  510. """
  511. return var_operation_return(js_expression=f"Math.ceil({value})", var_type=int)
  512. @var_operation
  513. def number_floor_operation(value: NumberVar):
  514. """Floor the number.
  515. Args:
  516. value: The number.
  517. Returns:
  518. The number floor operation.
  519. """
  520. return var_operation_return(js_expression=f"Math.floor({value})", var_type=int)
  521. @var_operation
  522. def number_trunc_operation(value: NumberVar):
  523. """Trunc the number.
  524. Args:
  525. value: The number.
  526. Returns:
  527. The number trunc operation.
  528. """
  529. return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int)
  530. class BooleanVar(NumberVar[bool], python_types=bool):
  531. """Base class for immutable boolean vars."""
  532. def __invert__(self):
  533. """NOT the boolean.
  534. Returns:
  535. The boolean NOT operation.
  536. """
  537. return boolean_not_operation(self)
  538. def __int__(self):
  539. """Convert the boolean to an int.
  540. Returns:
  541. The boolean to int operation.
  542. """
  543. return boolean_to_number_operation(self)
  544. def __pos__(self):
  545. """Convert the boolean to an int.
  546. Returns:
  547. The boolean to int operation.
  548. """
  549. return boolean_to_number_operation(self)
  550. def bool(self) -> BooleanVar:
  551. """Boolean conversion.
  552. Returns:
  553. The boolean value of the boolean.
  554. """
  555. return self
  556. def __lt__(self, other: Any):
  557. """Less than comparison.
  558. Args:
  559. other: The other boolean.
  560. Returns:
  561. The result of the comparison.
  562. """
  563. return +self < other
  564. def __le__(self, other: Any):
  565. """Less than or equal comparison.
  566. Args:
  567. other: The other boolean.
  568. Returns:
  569. The result of the comparison.
  570. """
  571. return +self <= other
  572. def __gt__(self, other: Any):
  573. """Greater than comparison.
  574. Args:
  575. other: The other boolean.
  576. Returns:
  577. The result of the comparison.
  578. """
  579. return +self > other
  580. def __ge__(self, other: Any):
  581. """Greater than or equal comparison.
  582. Args:
  583. other: The other boolean.
  584. Returns:
  585. The result of the comparison.
  586. """
  587. return +self >= other
  588. @var_operation
  589. def boolean_to_number_operation(value: BooleanVar):
  590. """Convert the boolean to a number.
  591. Args:
  592. value: The boolean.
  593. Returns:
  594. The boolean to number operation.
  595. """
  596. return var_operation_return(js_expression=f"Number({value})", var_type=int)
  597. def comparison_operator(
  598. func: Callable[[Var, Var], str],
  599. ) -> Callable[[Var | Any, Var | Any], BooleanVar]:
  600. """Decorator to create a comparison operation.
  601. Args:
  602. func: The comparison operation function.
  603. Returns:
  604. The comparison operation.
  605. """
  606. @var_operation
  607. def operation(lhs: Var, rhs: Var):
  608. return var_operation_return(
  609. js_expression=func(lhs, rhs),
  610. var_type=bool,
  611. )
  612. def wrapper(lhs: Var | Any, rhs: Var | Any) -> BooleanVar:
  613. """Create the comparison operation.
  614. Args:
  615. lhs: The first value.
  616. rhs: The second value.
  617. Returns:
  618. The comparison operation.
  619. """
  620. return operation(lhs, rhs)
  621. return wrapper
  622. @comparison_operator
  623. def greater_than_operation(lhs: Var, rhs: Var):
  624. """Greater than comparison.
  625. Args:
  626. lhs: The first value.
  627. rhs: The second value.
  628. Returns:
  629. The result of the comparison.
  630. """
  631. return f"({lhs} > {rhs})"
  632. @comparison_operator
  633. def greater_than_or_equal_operation(lhs: Var, rhs: Var):
  634. """Greater than or equal comparison.
  635. Args:
  636. lhs: The first value.
  637. rhs: The second value.
  638. Returns:
  639. The result of the comparison.
  640. """
  641. return f"({lhs} >= {rhs})"
  642. @comparison_operator
  643. def less_than_operation(lhs: Var, rhs: Var):
  644. """Less than comparison.
  645. Args:
  646. lhs: The first value.
  647. rhs: The second value.
  648. Returns:
  649. The result of the comparison.
  650. """
  651. return f"({lhs} < {rhs})"
  652. @comparison_operator
  653. def less_than_or_equal_operation(lhs: Var, rhs: Var):
  654. """Less than or equal comparison.
  655. Args:
  656. lhs: The first value.
  657. rhs: The second value.
  658. Returns:
  659. The result of the comparison.
  660. """
  661. return f"({lhs} <= {rhs})"
  662. @comparison_operator
  663. def equal_operation(lhs: Var, rhs: Var):
  664. """Equal comparison.
  665. Args:
  666. lhs: The first value.
  667. rhs: The second value.
  668. Returns:
  669. The result of the comparison.
  670. """
  671. return f"({lhs} === {rhs})"
  672. @comparison_operator
  673. def not_equal_operation(lhs: Var, rhs: Var):
  674. """Not equal comparison.
  675. Args:
  676. lhs: The first value.
  677. rhs: The second value.
  678. Returns:
  679. The result of the comparison.
  680. """
  681. return f"({lhs} !== {rhs})"
  682. @var_operation
  683. def boolean_not_operation(value: BooleanVar):
  684. """Boolean NOT the boolean.
  685. Args:
  686. value: The boolean.
  687. Returns:
  688. The boolean NOT operation.
  689. """
  690. return var_operation_return(js_expression=f"!({value})", var_type=bool)
  691. @dataclasses.dataclass(
  692. eq=False,
  693. frozen=True,
  694. slots=True,
  695. )
  696. class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]):
  697. """Base class for immutable literal number vars."""
  698. _var_value: float | int | decimal.Decimal = dataclasses.field(default=0)
  699. def json(self) -> str:
  700. """Get the JSON representation of the var.
  701. Returns:
  702. The JSON representation of the var.
  703. Raises:
  704. PrimitiveUnserializableToJSONError: If the var is unserializable to JSON.
  705. """
  706. if isinstance(self._var_value, decimal.Decimal):
  707. return json.dumps(float(self._var_value))
  708. if math.isinf(self._var_value) or math.isnan(self._var_value):
  709. msg = f"No valid JSON representation for {self}"
  710. raise PrimitiveUnserializableToJSONError(msg)
  711. return json.dumps(self._var_value)
  712. def __hash__(self) -> int:
  713. """Calculate the hash value of the object.
  714. Returns:
  715. int: The hash value of the object.
  716. """
  717. return hash((type(self).__name__, self._var_value))
  718. @classmethod
  719. def create(
  720. cls, value: float | int | decimal.Decimal, _var_data: VarData | None = None
  721. ):
  722. """Create the number var.
  723. Args:
  724. value: The value of the var.
  725. _var_data: Additional hooks and imports associated with the Var.
  726. Returns:
  727. The number var.
  728. """
  729. if math.isinf(value):
  730. js_expr = "Infinity" if value > 0 else "-Infinity"
  731. elif math.isnan(value):
  732. js_expr = "NaN"
  733. else:
  734. js_expr = str(value)
  735. return cls(
  736. _js_expr=js_expr,
  737. _var_type=type(value),
  738. _var_data=_var_data,
  739. _var_value=value,
  740. )
  741. @dataclasses.dataclass(
  742. eq=False,
  743. frozen=True,
  744. slots=True,
  745. )
  746. class LiteralBooleanVar(LiteralVar, BooleanVar):
  747. """Base class for immutable literal boolean vars."""
  748. _var_value: bool = dataclasses.field(default=False)
  749. def json(self) -> str:
  750. """Get the JSON representation of the var.
  751. Returns:
  752. The JSON representation of the var.
  753. """
  754. return "true" if self._var_value else "false"
  755. def __hash__(self) -> int:
  756. """Calculate the hash value of the object.
  757. Returns:
  758. int: The hash value of the object.
  759. """
  760. return hash((type(self).__name__, self._var_value))
  761. @classmethod
  762. def create(cls, value: bool, _var_data: VarData | None = None):
  763. """Create the boolean var.
  764. Args:
  765. value: The value of the var.
  766. _var_data: Additional hooks and imports associated with the Var.
  767. Returns:
  768. The boolean var.
  769. """
  770. return cls(
  771. _js_expr="true" if value else "false",
  772. _var_type=bool,
  773. _var_data=_var_data,
  774. _var_value=value,
  775. )
  776. number_types = NumberVar | int | float | decimal.Decimal
  777. boolean_types = BooleanVar | bool
  778. _IS_TRUE_IMPORT: ImportDict = {
  779. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
  780. }
  781. _IS_NOT_NULL_OR_UNDEFINED_IMPORT: ImportDict = {
  782. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isNotNullOrUndefined")],
  783. }
  784. @var_operation
  785. def boolify(value: Var):
  786. """Convert the value to a boolean.
  787. Args:
  788. value: The value.
  789. Returns:
  790. The boolean value.
  791. """
  792. return var_operation_return(
  793. js_expression=f"isTrue({value})",
  794. var_type=bool,
  795. var_data=VarData(imports=_IS_TRUE_IMPORT),
  796. )
  797. @var_operation
  798. def is_not_none_operation(value: Var):
  799. """Check if the value is not None.
  800. Args:
  801. value: The value.
  802. Returns:
  803. The boolean value.
  804. """
  805. return var_operation_return(
  806. js_expression=f"isNotNullOrUndefined({value})",
  807. var_type=bool,
  808. var_data=VarData(imports=_IS_NOT_NULL_OR_UNDEFINED_IMPORT),
  809. )
  810. T = TypeVar("T")
  811. U = TypeVar("U")
  812. @var_operation
  813. def ternary_operation(
  814. condition: Var[bool], if_true: Var[T], if_false: Var[U]
  815. ) -> CustomVarOperationReturn[T | U]:
  816. """Create a ternary operation.
  817. Args:
  818. condition: The condition.
  819. if_true: The value if the condition is true.
  820. if_false: The value if the condition is false.
  821. Returns:
  822. The ternary operation.
  823. """
  824. type_value: type[T] | type[U] = unionize(if_true._var_type, if_false._var_type)
  825. value: CustomVarOperationReturn[T | U] = var_operation_return(
  826. js_expression=f"({condition} ? {if_true} : {if_false})",
  827. var_type=type_value,
  828. )
  829. return value
  830. NUMBER_TYPES = (int, float, decimal.Decimal, NumberVar)