1
0

number.py 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  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. raise VarTypeError(
  46. f"Unsupported Operand type(s) for {operator}: {', '.join(t.__name__ for t in operands_types)}"
  47. )
  48. class NumberVar(Var[NUMBER_T], python_types=(int, float, decimal.Decimal)):
  49. """Base class for immutable number vars."""
  50. def __add__(self, other: number_types) -> NumberVar:
  51. """Add two numbers.
  52. Args:
  53. other: The other number.
  54. Returns:
  55. The number addition operation.
  56. """
  57. if not isinstance(other, NUMBER_TYPES):
  58. raise_unsupported_operand_types("+", (type(self), type(other)))
  59. return number_add_operation(self, +other)
  60. def __radd__(self, other: number_types) -> NumberVar:
  61. """Add two numbers.
  62. Args:
  63. other: The other number.
  64. Returns:
  65. The number addition operation.
  66. """
  67. if not isinstance(other, NUMBER_TYPES):
  68. raise_unsupported_operand_types("+", (type(other), type(self)))
  69. return number_add_operation(+other, self)
  70. def __sub__(self, other: number_types) -> NumberVar:
  71. """Subtract two numbers.
  72. Args:
  73. other: The other number.
  74. Returns:
  75. The number subtraction operation.
  76. """
  77. if not isinstance(other, NUMBER_TYPES):
  78. raise_unsupported_operand_types("-", (type(self), type(other)))
  79. return number_subtract_operation(self, +other)
  80. def __rsub__(self, other: number_types) -> NumberVar:
  81. """Subtract two numbers.
  82. Args:
  83. other: The other number.
  84. Returns:
  85. The number subtraction operation.
  86. """
  87. if not isinstance(other, NUMBER_TYPES):
  88. raise_unsupported_operand_types("-", (type(other), type(self)))
  89. return number_subtract_operation(+other, self)
  90. def __abs__(self):
  91. """Get the absolute value of the number.
  92. Returns:
  93. The number absolute operation.
  94. """
  95. return number_abs_operation(self)
  96. @overload
  97. def __mul__(self, other: number_types | boolean_types) -> NumberVar: ...
  98. @overload
  99. def __mul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  100. def __mul__(self, other: Any):
  101. """Multiply two numbers.
  102. Args:
  103. other: The other number.
  104. Returns:
  105. The number multiplication operation.
  106. """
  107. from .sequence import ArrayVar, LiteralArrayVar
  108. if isinstance(other, (list, tuple, ArrayVar)):
  109. if isinstance(other, ArrayVar):
  110. return other * self
  111. return LiteralArrayVar.create(other) * self
  112. if not isinstance(other, NUMBER_TYPES):
  113. raise_unsupported_operand_types("*", (type(self), type(other)))
  114. return number_multiply_operation(self, +other)
  115. @overload
  116. def __rmul__(self, other: number_types | boolean_types) -> NumberVar: ...
  117. @overload
  118. def __rmul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  119. def __rmul__(self, other: Any):
  120. """Multiply two numbers.
  121. Args:
  122. other: The other number.
  123. Returns:
  124. The number multiplication operation.
  125. """
  126. from .sequence import ArrayVar, LiteralArrayVar
  127. if isinstance(other, (list, tuple, ArrayVar)):
  128. if isinstance(other, ArrayVar):
  129. return other * self
  130. return LiteralArrayVar.create(other) * self
  131. if not isinstance(other, NUMBER_TYPES):
  132. raise_unsupported_operand_types("*", (type(other), type(self)))
  133. return number_multiply_operation(+other, self)
  134. def __truediv__(self, other: number_types) -> NumberVar:
  135. """Divide two numbers.
  136. Args:
  137. other: The other number.
  138. Returns:
  139. The number true division operation.
  140. """
  141. if not isinstance(other, NUMBER_TYPES):
  142. raise_unsupported_operand_types("/", (type(self), type(other)))
  143. return number_true_division_operation(self, +other)
  144. def __rtruediv__(self, other: number_types) -> NumberVar:
  145. """Divide two numbers.
  146. Args:
  147. other: The other number.
  148. Returns:
  149. The number true division operation.
  150. """
  151. if not isinstance(other, NUMBER_TYPES):
  152. raise_unsupported_operand_types("/", (type(other), type(self)))
  153. return number_true_division_operation(+other, self)
  154. def __floordiv__(self, other: number_types) -> NumberVar:
  155. """Floor divide two numbers.
  156. Args:
  157. other: The other number.
  158. Returns:
  159. The number floor division operation.
  160. """
  161. if not isinstance(other, NUMBER_TYPES):
  162. raise_unsupported_operand_types("//", (type(self), type(other)))
  163. return number_floor_division_operation(self, +other)
  164. def __rfloordiv__(self, other: number_types) -> NumberVar:
  165. """Floor divide two numbers.
  166. Args:
  167. other: The other number.
  168. Returns:
  169. The number floor division operation.
  170. """
  171. if not isinstance(other, NUMBER_TYPES):
  172. raise_unsupported_operand_types("//", (type(other), type(self)))
  173. return number_floor_division_operation(+other, self)
  174. def __mod__(self, other: number_types) -> NumberVar:
  175. """Modulo two numbers.
  176. Args:
  177. other: The other number.
  178. Returns:
  179. The number modulo operation.
  180. """
  181. if not isinstance(other, NUMBER_TYPES):
  182. raise_unsupported_operand_types("%", (type(self), type(other)))
  183. return number_modulo_operation(self, +other)
  184. def __rmod__(self, other: number_types) -> NumberVar:
  185. """Modulo two numbers.
  186. Args:
  187. other: The other number.
  188. Returns:
  189. The number modulo operation.
  190. """
  191. if not isinstance(other, NUMBER_TYPES):
  192. raise_unsupported_operand_types("%", (type(other), type(self)))
  193. return number_modulo_operation(+other, self)
  194. def __pow__(self, other: number_types) -> NumberVar:
  195. """Exponentiate two numbers.
  196. Args:
  197. other: The other number.
  198. Returns:
  199. The number exponent operation.
  200. """
  201. if not isinstance(other, NUMBER_TYPES):
  202. raise_unsupported_operand_types("**", (type(self), type(other)))
  203. return number_exponent_operation(self, +other)
  204. def __rpow__(self, other: number_types) -> NumberVar:
  205. """Exponentiate two numbers.
  206. Args:
  207. other: The other number.
  208. Returns:
  209. The number exponent operation.
  210. """
  211. if not isinstance(other, NUMBER_TYPES):
  212. raise_unsupported_operand_types("**", (type(other), type(self)))
  213. return number_exponent_operation(+other, self)
  214. def __neg__(self) -> NumberVar:
  215. """Negate the number.
  216. Returns:
  217. The number negation operation.
  218. """
  219. return number_negate_operation(self) # pyright: ignore [reportReturnType]
  220. def __invert__(self):
  221. """Boolean NOT the number.
  222. Returns:
  223. The boolean NOT operation.
  224. """
  225. return boolean_not_operation(self.bool())
  226. def __pos__(self) -> NumberVar:
  227. """Positive the number.
  228. Returns:
  229. The number.
  230. """
  231. return self
  232. def __round__(self, ndigits: int | NumberVar = 0) -> NumberVar:
  233. """Round the number.
  234. Args:
  235. ndigits: The number of digits to round.
  236. Returns:
  237. The number round operation.
  238. """
  239. if not isinstance(ndigits, NUMBER_TYPES):
  240. raise_unsupported_operand_types("round", (type(self), type(ndigits)))
  241. return number_round_operation(self, +ndigits)
  242. def __ceil__(self):
  243. """Ceil the number.
  244. Returns:
  245. The number ceil operation.
  246. """
  247. return number_ceil_operation(self)
  248. def __floor__(self):
  249. """Floor the number.
  250. Returns:
  251. The number floor operation.
  252. """
  253. return number_floor_operation(self)
  254. def __trunc__(self):
  255. """Trunc the number.
  256. Returns:
  257. The number trunc operation.
  258. """
  259. return number_trunc_operation(self)
  260. def __lt__(self, other: number_types) -> BooleanVar:
  261. """Less than comparison.
  262. Args:
  263. other: The other number.
  264. Returns:
  265. The result of the comparison.
  266. """
  267. if not isinstance(other, NUMBER_TYPES):
  268. raise_unsupported_operand_types("<", (type(self), type(other)))
  269. return less_than_operation(+self, +other)
  270. def __le__(self, other: number_types) -> BooleanVar:
  271. """Less than or equal comparison.
  272. Args:
  273. other: The other number.
  274. Returns:
  275. The result of the comparison.
  276. """
  277. if not isinstance(other, NUMBER_TYPES):
  278. raise_unsupported_operand_types("<=", (type(self), type(other)))
  279. return less_than_or_equal_operation(+self, +other)
  280. def __eq__(self, other: Any):
  281. """Equal comparison.
  282. Args:
  283. other: The other number.
  284. Returns:
  285. The result of the comparison.
  286. """
  287. if isinstance(other, NUMBER_TYPES):
  288. return equal_operation(+self, +other)
  289. return equal_operation(self, other)
  290. def __ne__(self, other: Any):
  291. """Not equal comparison.
  292. Args:
  293. other: The other number.
  294. Returns:
  295. The result of the comparison.
  296. """
  297. if isinstance(other, NUMBER_TYPES):
  298. return not_equal_operation(+self, +other)
  299. return not_equal_operation(self, other)
  300. def __gt__(self, other: number_types) -> BooleanVar:
  301. """Greater than comparison.
  302. Args:
  303. other: The other number.
  304. Returns:
  305. The result of the comparison.
  306. """
  307. if not isinstance(other, NUMBER_TYPES):
  308. raise_unsupported_operand_types(">", (type(self), type(other)))
  309. return greater_than_operation(+self, +other)
  310. def __ge__(self, other: number_types) -> BooleanVar:
  311. """Greater than or equal comparison.
  312. Args:
  313. other: The other number.
  314. Returns:
  315. The result of the comparison.
  316. """
  317. if not isinstance(other, NUMBER_TYPES):
  318. raise_unsupported_operand_types(">=", (type(self), type(other)))
  319. return greater_than_or_equal_operation(+self, +other)
  320. def _is_strict_float(self) -> bool:
  321. """Check if the number is a float.
  322. Returns:
  323. bool: True if the number is a float.
  324. """
  325. return safe_issubclass(self._var_type, float)
  326. def _is_strict_int(self) -> bool:
  327. """Check if the number is an int.
  328. Returns:
  329. bool: True if the number is an int.
  330. """
  331. return safe_issubclass(self._var_type, int)
  332. def __format__(self, format_spec: str) -> str:
  333. """Format the number.
  334. Args:
  335. format_spec: The format specifier.
  336. Returns:
  337. The formatted number.
  338. Raises:
  339. VarValueError: If the format specifier is not supported.
  340. """
  341. from .sequence import (
  342. get_decimal_string_operation,
  343. get_decimal_string_separator_operation,
  344. )
  345. separator = ""
  346. if format_spec and format_spec[:1] == ",":
  347. separator = ","
  348. format_spec = format_spec[1:]
  349. elif format_spec and format_spec[:1] == "_":
  350. separator = "_"
  351. format_spec = format_spec[1:]
  352. if (
  353. format_spec
  354. and format_spec[-1] == "f"
  355. and format_spec[0] == "."
  356. and format_spec[1:-1].isdigit()
  357. ):
  358. how_many_decimals = int(format_spec[1:-1])
  359. return f"{get_decimal_string_operation(self, Var.create(how_many_decimals), Var.create(separator))}"
  360. if not format_spec and separator:
  361. return (
  362. f"{get_decimal_string_separator_operation(self, Var.create(separator))}"
  363. )
  364. if format_spec:
  365. raise VarValueError(
  366. f"Unknown format code '{format_spec}' for object of type 'NumberVar'. It is only supported to use ',', '_', and '.f' for float numbers."
  367. "If possible, use computed variables instead: https://reflex.dev/docs/vars/computed-vars/"
  368. )
  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. raise PrimitiveUnserializableToJSONError(
  710. f"No valid JSON representation for {self}"
  711. )
  712. return json.dumps(self._var_value)
  713. def __hash__(self) -> int:
  714. """Calculate the hash value of the object.
  715. Returns:
  716. int: The hash value of the object.
  717. """
  718. return hash((type(self).__name__, self._var_value))
  719. @classmethod
  720. def create(
  721. cls, value: float | int | decimal.Decimal, _var_data: VarData | None = None
  722. ):
  723. """Create the number var.
  724. Args:
  725. value: The value of the var.
  726. _var_data: Additional hooks and imports associated with the Var.
  727. Returns:
  728. The number var.
  729. """
  730. if math.isinf(value):
  731. js_expr = "Infinity" if value > 0 else "-Infinity"
  732. elif math.isnan(value):
  733. js_expr = "NaN"
  734. else:
  735. js_expr = str(value)
  736. return cls(
  737. _js_expr=js_expr,
  738. _var_type=type(value),
  739. _var_data=_var_data,
  740. _var_value=value,
  741. )
  742. @dataclasses.dataclass(
  743. eq=False,
  744. frozen=True,
  745. slots=True,
  746. )
  747. class LiteralBooleanVar(LiteralVar, BooleanVar):
  748. """Base class for immutable literal boolean vars."""
  749. _var_value: bool = dataclasses.field(default=False)
  750. def json(self) -> str:
  751. """Get the JSON representation of the var.
  752. Returns:
  753. The JSON representation of the var.
  754. """
  755. return "true" if self._var_value else "false"
  756. def __hash__(self) -> int:
  757. """Calculate the hash value of the object.
  758. Returns:
  759. int: The hash value of the object.
  760. """
  761. return hash((type(self).__name__, self._var_value))
  762. @classmethod
  763. def create(cls, value: bool, _var_data: VarData | None = None):
  764. """Create the boolean var.
  765. Args:
  766. value: The value of the var.
  767. _var_data: Additional hooks and imports associated with the Var.
  768. Returns:
  769. The boolean var.
  770. """
  771. return cls(
  772. _js_expr="true" if value else "false",
  773. _var_type=bool,
  774. _var_data=_var_data,
  775. _var_value=value,
  776. )
  777. number_types = NumberVar | int | float | decimal.Decimal
  778. boolean_types = BooleanVar | bool
  779. _IS_TRUE_IMPORT: ImportDict = {
  780. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
  781. }
  782. _IS_NOT_NULL_OR_UNDEFINED_IMPORT: ImportDict = {
  783. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isNotNullOrUndefined")],
  784. }
  785. @var_operation
  786. def boolify(value: Var):
  787. """Convert the value to a boolean.
  788. Args:
  789. value: The value.
  790. Returns:
  791. The boolean value.
  792. """
  793. return var_operation_return(
  794. js_expression=f"isTrue({value})",
  795. var_type=bool,
  796. var_data=VarData(imports=_IS_TRUE_IMPORT),
  797. )
  798. @var_operation
  799. def is_not_none_operation(value: Var):
  800. """Check if the value is not None.
  801. Args:
  802. value: The value.
  803. Returns:
  804. The boolean value.
  805. """
  806. return var_operation_return(
  807. js_expression=f"isNotNullOrUndefined({value})",
  808. var_type=bool,
  809. var_data=VarData(imports=_IS_NOT_NULL_OR_UNDEFINED_IMPORT),
  810. )
  811. T = TypeVar("T")
  812. U = TypeVar("U")
  813. @var_operation
  814. def ternary_operation(
  815. condition: Var[bool], if_true: Var[T], if_false: Var[U]
  816. ) -> CustomVarOperationReturn[T | U]:
  817. """Create a ternary operation.
  818. Args:
  819. condition: The condition.
  820. if_true: The value if the condition is true.
  821. if_false: The value if the condition is false.
  822. Returns:
  823. The ternary operation.
  824. """
  825. type_value: type[T] | type[U] = unionize(if_true._var_type, if_false._var_type)
  826. value: CustomVarOperationReturn[T | U] = var_operation_return(
  827. js_expression=f"({condition} ? {if_true} : {if_false})",
  828. var_type=type_value,
  829. )
  830. return value
  831. NUMBER_TYPES = (int, float, decimal.Decimal, NumberVar)