base.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. """Collection of base classes."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import json
  5. import re
  6. import sys
  7. from functools import cached_property
  8. from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
  9. from reflex import constants
  10. from reflex.base import Base
  11. from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
  12. from reflex.utils import serializers, types
  13. from reflex.utils.exceptions import VarTypeError
  14. from reflex.vars import (
  15. ImmutableVarData,
  16. Var,
  17. VarData,
  18. _decode_var_immutable,
  19. _extract_var_data,
  20. _global_vars,
  21. )
  22. @dataclasses.dataclass(
  23. eq=False,
  24. frozen=True,
  25. **{"slots": True} if sys.version_info >= (3, 10) else {},
  26. )
  27. class ImmutableVar(Var):
  28. """Base class for immutable vars."""
  29. # The name of the var.
  30. _var_name: str = dataclasses.field()
  31. # The type of the var.
  32. _var_type: Type = dataclasses.field(default=Any)
  33. # Extra metadata associated with the Var
  34. _var_data: Optional[ImmutableVarData] = dataclasses.field(default=None)
  35. def __str__(self) -> str:
  36. """String representation of the var. Guaranteed to be a valid Javascript expression.
  37. Returns:
  38. The name of the var.
  39. """
  40. return self._var_name
  41. @property
  42. def _var_is_local(self) -> bool:
  43. """Whether this is a local javascript variable.
  44. Returns:
  45. False
  46. """
  47. return False
  48. @property
  49. def _var_is_string(self) -> bool:
  50. """Whether the var is a string literal.
  51. Returns:
  52. False
  53. """
  54. return False
  55. @property
  56. def _var_full_name_needs_state_prefix(self) -> bool:
  57. """Whether the full name of the var needs a _var_state prefix.
  58. Returns:
  59. False
  60. """
  61. return False
  62. def __post_init__(self):
  63. """Post-initialize the var."""
  64. # Decode any inline Var markup and apply it to the instance
  65. _var_data, _var_name = _decode_var_immutable(self._var_name)
  66. if _var_data:
  67. self.__init__(
  68. _var_name,
  69. self._var_type,
  70. ImmutableVarData.merge(self._var_data, _var_data),
  71. )
  72. def __hash__(self) -> int:
  73. """Define a hash function for the var.
  74. Returns:
  75. The hash of the var.
  76. """
  77. return hash((self._var_name, self._var_type, self._var_data))
  78. def _get_all_var_data(self) -> ImmutableVarData | None:
  79. """Get all VarData associated with the Var.
  80. Returns:
  81. The VarData of the components and all of its children.
  82. """
  83. return self._var_data
  84. def _replace(self, merge_var_data=None, **kwargs: Any):
  85. """Make a copy of this Var with updated fields.
  86. Args:
  87. merge_var_data: VarData to merge into the existing VarData.
  88. **kwargs: Var fields to update.
  89. Returns:
  90. A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
  91. Raises:
  92. TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
  93. """
  94. if kwargs.get("_var_is_local", False) is not False:
  95. raise TypeError(
  96. "The _var_is_local argument is not supported for ImmutableVar."
  97. )
  98. if kwargs.get("_var_is_string", False) is not False:
  99. raise TypeError(
  100. "The _var_is_string argument is not supported for ImmutableVar."
  101. )
  102. if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
  103. raise TypeError(
  104. "The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
  105. )
  106. field_values = dict(
  107. _var_name=kwargs.pop("_var_name", self._var_name),
  108. _var_type=kwargs.pop("_var_type", self._var_type),
  109. _var_data=ImmutableVarData.merge(
  110. kwargs.get("_var_data", self._var_data), merge_var_data
  111. ),
  112. )
  113. return type(self)(**field_values)
  114. @classmethod
  115. def create(
  116. cls,
  117. value: Any,
  118. _var_is_local: bool | None = None,
  119. _var_is_string: bool | None = None,
  120. _var_data: VarData | None = None,
  121. ) -> ImmutableVar | Var | None:
  122. """Create a var from a value.
  123. Args:
  124. value: The value to create the var from.
  125. _var_is_local: Whether the var is local. Deprecated.
  126. _var_is_string: Whether the var is a string literal. Deprecated.
  127. _var_data: Additional hooks and imports associated with the Var.
  128. Returns:
  129. The var.
  130. Raises:
  131. VarTypeError: If the value is JSON-unserializable.
  132. TypeError: If _var_is_local or _var_is_string is not None.
  133. """
  134. if _var_is_local is not None:
  135. raise TypeError(
  136. "The _var_is_local argument is not supported for ImmutableVar."
  137. )
  138. if _var_is_string is not None:
  139. raise TypeError(
  140. "The _var_is_string argument is not supported for ImmutableVar."
  141. )
  142. from reflex.utils import format
  143. # Check for none values.
  144. if value is None:
  145. return None
  146. # If the value is already a var, do nothing.
  147. if isinstance(value, Var):
  148. return value
  149. # Try to pull the imports and hooks from contained values.
  150. if not isinstance(value, str):
  151. _var_data = VarData.merge(*_extract_var_data(value), _var_data)
  152. # Try to serialize the value.
  153. type_ = type(value)
  154. if type_ in types.JSONType:
  155. name = value
  156. else:
  157. name, _serialized_type = serializers.serialize(value, get_type=True)
  158. if name is None:
  159. raise VarTypeError(
  160. f"No JSON serializer found for var {value} of type {type_}."
  161. )
  162. name = name if isinstance(name, str) else format.json_dumps(name)
  163. return cls(
  164. _var_name=name,
  165. _var_type=type_,
  166. _var_data=(
  167. ImmutableVarData(
  168. state=_var_data.state,
  169. imports=_var_data.imports,
  170. hooks=_var_data.hooks,
  171. )
  172. if _var_data
  173. else None
  174. ),
  175. )
  176. @classmethod
  177. def create_safe(
  178. cls,
  179. value: Any,
  180. _var_is_local: bool | None = None,
  181. _var_is_string: bool | None = None,
  182. _var_data: VarData | None = None,
  183. ) -> Var | ImmutableVar:
  184. """Create a var from a value, asserting that it is not None.
  185. Args:
  186. value: The value to create the var from.
  187. _var_is_local: Whether the var is local. Deprecated.
  188. _var_is_string: Whether the var is a string literal. Deprecated.
  189. _var_data: Additional hooks and imports associated with the Var.
  190. Returns:
  191. The var.
  192. """
  193. var = cls.create(
  194. value,
  195. _var_is_local=_var_is_local,
  196. _var_is_string=_var_is_string,
  197. _var_data=_var_data,
  198. )
  199. assert var is not None
  200. return var
  201. def __format__(self, format_spec: str) -> str:
  202. """Format the var into a Javascript equivalent to an f-string.
  203. Args:
  204. format_spec: The format specifier (Ignored for now).
  205. Returns:
  206. The formatted var.
  207. """
  208. hashed_var = hash(self)
  209. _global_vars[hashed_var] = self
  210. # Encode the _var_data into the formatted output for tracking purposes.
  211. return f"{REFLEX_VAR_OPENING_TAG}{hashed_var}{REFLEX_VAR_CLOSING_TAG}{self._var_name}"
  212. class StringVar(ImmutableVar):
  213. """Base class for immutable string vars."""
  214. class NumberVar(ImmutableVar):
  215. """Base class for immutable number vars."""
  216. class BooleanVar(ImmutableVar):
  217. """Base class for immutable boolean vars."""
  218. class ObjectVar(ImmutableVar):
  219. """Base class for immutable object vars."""
  220. class ArrayVar(ImmutableVar):
  221. """Base class for immutable array vars."""
  222. class FunctionVar(ImmutableVar):
  223. """Base class for immutable function vars."""
  224. def __call__(self, *args: Var | Any) -> ArgsFunctionOperation:
  225. """Call the function with the given arguments.
  226. Args:
  227. *args: The arguments to call the function with.
  228. Returns:
  229. The function call operation.
  230. """
  231. return ArgsFunctionOperation(
  232. ("...args",),
  233. VarOperationCall(self, *args, ImmutableVar.create_safe("...args")),
  234. )
  235. def call(self, *args: Var | Any) -> VarOperationCall:
  236. """Call the function with the given arguments.
  237. Args:
  238. *args: The arguments to call the function with.
  239. Returns:
  240. The function call operation.
  241. """
  242. return VarOperationCall(self, *args)
  243. class FunctionStringVar(FunctionVar):
  244. """Base class for immutable function vars from a string."""
  245. def __init__(self, func: str, _var_data: VarData | None = None) -> None:
  246. """Initialize the function var.
  247. Args:
  248. func: The function to call.
  249. _var_data: Additional hooks and imports associated with the Var.
  250. """
  251. super(FunctionVar, self).__init__(
  252. _var_name=func,
  253. _var_type=Callable,
  254. _var_data=ImmutableVarData.merge(_var_data),
  255. )
  256. @dataclasses.dataclass(
  257. eq=False,
  258. frozen=True,
  259. **{"slots": True} if sys.version_info >= (3, 10) else {},
  260. )
  261. class VarOperationCall(ImmutableVar):
  262. """Base class for immutable vars that are the result of a function call."""
  263. _func: Optional[FunctionVar] = dataclasses.field(default=None)
  264. _args: Tuple[Union[Var, Any], ...] = dataclasses.field(default_factory=tuple)
  265. def __init__(
  266. self, func: FunctionVar, *args: Var | Any, _var_data: VarData | None = None
  267. ):
  268. """Initialize the function call var.
  269. Args:
  270. func: The function to call.
  271. *args: The arguments to call the function with.
  272. _var_data: Additional hooks and imports associated with the Var.
  273. """
  274. super(VarOperationCall, self).__init__(
  275. _var_name="",
  276. _var_type=Callable,
  277. _var_data=ImmutableVarData.merge(_var_data),
  278. )
  279. object.__setattr__(self, "_func", func)
  280. object.__setattr__(self, "_args", args)
  281. object.__delattr__(self, "_var_name")
  282. def __getattr__(self, name):
  283. """Get an attribute of the var.
  284. Args:
  285. name: The name of the attribute.
  286. Returns:
  287. The attribute of the var.
  288. """
  289. if name == "_var_name":
  290. return self._cached_var_name
  291. return super(type(self), self).__getattr__(name)
  292. @cached_property
  293. def _cached_var_name(self) -> str:
  294. """The name of the var.
  295. Returns:
  296. The name of the var.
  297. """
  298. return f"({str(self._func)}({', '.join([str(LiteralVar.create(arg)) for arg in self._args])}))"
  299. @cached_property
  300. def _cached_get_all_var_data(self) -> ImmutableVarData | None:
  301. """Get all VarData associated with the Var.
  302. Returns:
  303. The VarData of the components and all of its children.
  304. """
  305. return ImmutableVarData.merge(
  306. self._func._get_all_var_data() if self._func is not None else None,
  307. *[var._get_all_var_data() for var in self._args],
  308. self._var_data,
  309. )
  310. def _get_all_var_data(self) -> ImmutableVarData | None:
  311. """Wrapper method for cached property.
  312. Returns:
  313. The VarData of the components and all of its children.
  314. """
  315. return self._cached_get_all_var_data
  316. def __post_init__(self):
  317. """Post-initialize the var."""
  318. pass
  319. @dataclasses.dataclass(
  320. eq=False,
  321. frozen=True,
  322. **{"slots": True} if sys.version_info >= (3, 10) else {},
  323. )
  324. class ArgsFunctionOperation(FunctionVar):
  325. """Base class for immutable function defined via arguments and return expression."""
  326. _args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
  327. _return_expr: Union[Var, Any] = dataclasses.field(default=None)
  328. def __init__(
  329. self,
  330. args_names: Tuple[str, ...],
  331. return_expr: Var | Any,
  332. _var_data: VarData | None = None,
  333. ) -> None:
  334. """Initialize the function with arguments var.
  335. Args:
  336. args_names: The names of the arguments.
  337. return_expr: The return expression of the function.
  338. _var_data: Additional hooks and imports associated with the Var.
  339. """
  340. super(ArgsFunctionOperation, self).__init__(
  341. _var_name=f"",
  342. _var_type=Callable,
  343. _var_data=ImmutableVarData.merge(_var_data),
  344. )
  345. object.__setattr__(self, "_args_names", args_names)
  346. object.__setattr__(self, "_return_expr", return_expr)
  347. object.__delattr__(self, "_var_name")
  348. def __getattr__(self, name):
  349. """Get an attribute of the var.
  350. Args:
  351. name: The name of the attribute.
  352. Returns:
  353. The attribute of the var.
  354. """
  355. if name == "_var_name":
  356. return self._cached_var_name
  357. return super(type(self), self).__getattr__(name)
  358. @cached_property
  359. def _cached_var_name(self) -> str:
  360. """The name of the var.
  361. Returns:
  362. The name of the var.
  363. """
  364. return f"(({', '.join(self._args_names)}) => ({str(LiteralVar.create(self._return_expr))}))"
  365. @cached_property
  366. def _cached_get_all_var_data(self) -> ImmutableVarData | None:
  367. """Get all VarData associated with the Var.
  368. Returns:
  369. The VarData of the components and all of its children.
  370. """
  371. return ImmutableVarData.merge(
  372. self._return_expr._get_all_var_data(),
  373. self._var_data,
  374. )
  375. def _get_all_var_data(self) -> ImmutableVarData | None:
  376. """Wrapper method for cached property.
  377. Returns:
  378. The VarData of the components and all of its children.
  379. """
  380. return self._cached_get_all_var_data
  381. def __post_init__(self):
  382. """Post-initialize the var."""
  383. class LiteralVar(ImmutableVar):
  384. """Base class for immutable literal vars."""
  385. @classmethod
  386. def create(
  387. cls,
  388. value: Any,
  389. _var_data: VarData | None = None,
  390. ) -> Var:
  391. """Create a var from a value.
  392. Args:
  393. value: The value to create the var from.
  394. _var_data: Additional hooks and imports associated with the Var.
  395. Returns:
  396. The var.
  397. Raises:
  398. TypeError: If the value is not a supported type for LiteralVar.
  399. """
  400. if isinstance(value, Var):
  401. if _var_data is None:
  402. return value
  403. return value._replace(merge_var_data=_var_data)
  404. if value is None:
  405. return ImmutableVar.create_safe("null", _var_data=_var_data)
  406. if isinstance(value, Base):
  407. return LiteralObjectVar(
  408. value.dict(), _var_type=type(value), _var_data=_var_data
  409. )
  410. if isinstance(value, str):
  411. return LiteralStringVar.create(value, _var_data=_var_data)
  412. constructor = type_mapping.get(type(value))
  413. if constructor is None:
  414. raise TypeError(f"Unsupported type {type(value)} for LiteralVar.")
  415. return constructor(value, _var_data=_var_data)
  416. def __post_init__(self):
  417. """Post-initialize the var."""
  418. # Compile regex for finding reflex var tags.
  419. _decode_var_pattern_re = (
  420. rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
  421. )
  422. _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
  423. @dataclasses.dataclass(
  424. eq=False,
  425. frozen=True,
  426. **{"slots": True} if sys.version_info >= (3, 10) else {},
  427. )
  428. class LiteralStringVar(LiteralVar):
  429. """Base class for immutable literal string vars."""
  430. _var_value: str = dataclasses.field(default="")
  431. def __init__(
  432. self,
  433. _var_value: str,
  434. _var_data: VarData | None = None,
  435. ):
  436. """Initialize the string var.
  437. Args:
  438. _var_value: The value of the var.
  439. _var_data: Additional hooks and imports associated with the Var.
  440. """
  441. super(LiteralStringVar, self).__init__(
  442. _var_name=f'"{_var_value}"',
  443. _var_type=str,
  444. _var_data=ImmutableVarData.merge(_var_data),
  445. )
  446. object.__setattr__(self, "_var_value", _var_value)
  447. @classmethod
  448. def create(
  449. cls,
  450. value: str,
  451. _var_data: VarData | None = None,
  452. ) -> LiteralStringVar | ConcatVarOperation:
  453. """Create a var from a string value.
  454. Args:
  455. value: The value to create the var from.
  456. _var_data: Additional hooks and imports associated with the Var.
  457. Returns:
  458. The var.
  459. """
  460. if REFLEX_VAR_OPENING_TAG in value:
  461. strings_and_vals: list[Var | str] = []
  462. offset = 0
  463. # Initialize some methods for reading json.
  464. var_data_config = VarData().__config__
  465. def json_loads(s):
  466. try:
  467. return var_data_config.json_loads(s)
  468. except json.decoder.JSONDecodeError:
  469. return var_data_config.json_loads(
  470. var_data_config.json_loads(f'"{s}"')
  471. )
  472. # Find all tags.
  473. while m := _decode_var_pattern.search(value):
  474. start, end = m.span()
  475. if start > 0:
  476. strings_and_vals.append(value[:start])
  477. serialized_data = m.group(1)
  478. if serialized_data[1:].isnumeric():
  479. # This is a global immutable var.
  480. var = _global_vars[int(serialized_data)]
  481. strings_and_vals.append(var)
  482. value = value[(end + len(var._var_name)) :]
  483. else:
  484. data = json_loads(serialized_data)
  485. string_length = data.pop("string_length", None)
  486. var_data = VarData.parse_obj(data)
  487. # Use string length to compute positions of interpolations.
  488. if string_length is not None:
  489. realstart = start + offset
  490. var_data.interpolations = [
  491. (realstart, realstart + string_length)
  492. ]
  493. strings_and_vals.append(
  494. ImmutableVar.create_safe(
  495. value[end : (end + string_length)], _var_data=var_data
  496. )
  497. )
  498. value = value[(end + string_length) :]
  499. offset += end - start
  500. if value:
  501. strings_and_vals.append(value)
  502. return ConcatVarOperation(*strings_and_vals, _var_data=_var_data)
  503. return LiteralStringVar(
  504. value,
  505. _var_data=_var_data,
  506. )
  507. @dataclasses.dataclass(
  508. eq=False,
  509. frozen=True,
  510. **{"slots": True} if sys.version_info >= (3, 10) else {},
  511. )
  512. class ConcatVarOperation(StringVar):
  513. """Representing a concatenation of literal string vars."""
  514. _var_value: Tuple[Union[Var, str], ...] = dataclasses.field(default_factory=tuple)
  515. def __init__(self, *value: Var | str, _var_data: VarData | None = None):
  516. """Initialize the operation of concatenating literal string vars.
  517. Args:
  518. value: The values to concatenate.
  519. _var_data: Additional hooks and imports associated with the Var.
  520. """
  521. super(ConcatVarOperation, self).__init__(
  522. _var_name="", _var_data=ImmutableVarData.merge(_var_data), _var_type=str
  523. )
  524. object.__setattr__(self, "_var_value", value)
  525. object.__delattr__(self, "_var_name")
  526. def __getattr__(self, name):
  527. """Get an attribute of the var.
  528. Args:
  529. name: The name of the attribute.
  530. Returns:
  531. The attribute of the var.
  532. """
  533. if name == "_var_name":
  534. return self._cached_var_name
  535. return super(type(self), self).__getattr__(name)
  536. @cached_property
  537. def _cached_var_name(self) -> str:
  538. """The name of the var.
  539. Returns:
  540. The name of the var.
  541. """
  542. return (
  543. "("
  544. + "+".join(
  545. [
  546. str(element) if isinstance(element, Var) else f'"{element}"'
  547. for element in self._var_value
  548. ]
  549. )
  550. + ")"
  551. )
  552. @cached_property
  553. def _cached_get_all_var_data(self) -> ImmutableVarData | None:
  554. """Get all VarData associated with the Var.
  555. Returns:
  556. The VarData of the components and all of its children.
  557. """
  558. return ImmutableVarData.merge(
  559. *[
  560. var._get_all_var_data()
  561. for var in self._var_value
  562. if isinstance(var, Var)
  563. ],
  564. self._var_data,
  565. )
  566. def _get_all_var_data(self) -> ImmutableVarData | None:
  567. """Wrapper method for cached property.
  568. Returns:
  569. The VarData of the components and all of its children.
  570. """
  571. return self._cached_get_all_var_data
  572. def __post_init__(self):
  573. """Post-initialize the var."""
  574. pass
  575. @dataclasses.dataclass(
  576. eq=False,
  577. frozen=True,
  578. **{"slots": True} if sys.version_info >= (3, 10) else {},
  579. )
  580. class LiteralBooleanVar(LiteralVar):
  581. """Base class for immutable literal boolean vars."""
  582. _var_value: bool = dataclasses.field(default=False)
  583. def __init__(
  584. self,
  585. _var_value: bool,
  586. _var_data: VarData | None = None,
  587. ):
  588. """Initialize the boolean var.
  589. Args:
  590. _var_value: The value of the var.
  591. _var_data: Additional hooks and imports associated with the Var.
  592. """
  593. super(LiteralBooleanVar, self).__init__(
  594. _var_name="true" if _var_value else "false",
  595. _var_type=bool,
  596. _var_data=ImmutableVarData.merge(_var_data),
  597. )
  598. object.__setattr__(self, "_var_value", _var_value)
  599. @dataclasses.dataclass(
  600. eq=False,
  601. frozen=True,
  602. **{"slots": True} if sys.version_info >= (3, 10) else {},
  603. )
  604. class LiteralNumberVar(LiteralVar):
  605. """Base class for immutable literal number vars."""
  606. _var_value: float | int = dataclasses.field(default=0)
  607. def __init__(
  608. self,
  609. _var_value: float | int,
  610. _var_data: VarData | None = None,
  611. ):
  612. """Initialize the number var.
  613. Args:
  614. _var_value: The value of the var.
  615. _var_data: Additional hooks and imports associated with the Var.
  616. """
  617. super(LiteralNumberVar, self).__init__(
  618. _var_name=str(_var_value),
  619. _var_type=type(_var_value),
  620. _var_data=ImmutableVarData.merge(_var_data),
  621. )
  622. object.__setattr__(self, "_var_value", _var_value)
  623. @dataclasses.dataclass(
  624. eq=False,
  625. frozen=True,
  626. **{"slots": True} if sys.version_info >= (3, 10) else {},
  627. )
  628. class LiteralObjectVar(LiteralVar):
  629. """Base class for immutable literal object vars."""
  630. _var_value: Dict[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
  631. default_factory=dict
  632. )
  633. def __init__(
  634. self,
  635. _var_value: dict[Var | Any, Var | Any],
  636. _var_type: Type = dict,
  637. _var_data: VarData | None = None,
  638. ):
  639. """Initialize the object var.
  640. Args:
  641. _var_value: The value of the var.
  642. _var_data: Additional hooks and imports associated with the Var.
  643. """
  644. super(LiteralObjectVar, self).__init__(
  645. _var_name="",
  646. _var_type=_var_type,
  647. _var_data=ImmutableVarData.merge(_var_data),
  648. )
  649. object.__setattr__(
  650. self,
  651. "_var_value",
  652. _var_value,
  653. )
  654. object.__delattr__(self, "_var_name")
  655. def __getattr__(self, name):
  656. """Get an attribute of the var.
  657. Args:
  658. name: The name of the attribute.
  659. Returns:
  660. The attribute of the var.
  661. """
  662. if name == "_var_name":
  663. return self._cached_var_name
  664. return super(type(self), self).__getattr__(name)
  665. @cached_property
  666. def _cached_var_name(self) -> str:
  667. """The name of the var.
  668. Returns:
  669. The name of the var.
  670. """
  671. return (
  672. "{ "
  673. + ", ".join(
  674. [
  675. f"[{str(LiteralVar.create(key))}] : {str(LiteralVar.create(value))}"
  676. for key, value in self._var_value.items()
  677. ]
  678. )
  679. + " }"
  680. )
  681. @cached_property
  682. def _get_all_var_data(self) -> ImmutableVarData | None:
  683. """Get all VarData associated with the Var.
  684. Returns:
  685. The VarData of the components and all of its children.
  686. """
  687. return ImmutableVarData.merge(
  688. *[
  689. value._get_all_var_data()
  690. for key, value in self._var_value
  691. if isinstance(value, Var)
  692. ],
  693. *[
  694. key._get_all_var_data()
  695. for key, value in self._var_value
  696. if isinstance(key, Var)
  697. ],
  698. self._var_data,
  699. )
  700. @dataclasses.dataclass(
  701. eq=False,
  702. frozen=True,
  703. **{"slots": True} if sys.version_info >= (3, 10) else {},
  704. )
  705. class LiteralArrayVar(LiteralVar):
  706. """Base class for immutable literal array vars."""
  707. _var_value: Union[
  708. List[Union[Var, Any]], Set[Union[Var, Any]], Tuple[Union[Var, Any], ...]
  709. ] = dataclasses.field(default_factory=list)
  710. def __init__(
  711. self,
  712. _var_value: list[Var | Any] | tuple[Var | Any] | set[Var | Any],
  713. _var_data: VarData | None = None,
  714. ):
  715. """Initialize the array var.
  716. Args:
  717. _var_value: The value of the var.
  718. _var_data: Additional hooks and imports associated with the Var.
  719. """
  720. super(LiteralArrayVar, self).__init__(
  721. _var_name="",
  722. _var_data=ImmutableVarData.merge(_var_data),
  723. _var_type=list,
  724. )
  725. object.__setattr__(self, "_var_value", _var_value)
  726. object.__delattr__(self, "_var_name")
  727. def __getattr__(self, name):
  728. """Get an attribute of the var.
  729. Args:
  730. name: The name of the attribute.
  731. Returns:
  732. The attribute of the var.
  733. """
  734. if name == "_var_name":
  735. return self._cached_var_name
  736. return super(type(self), self).__getattr__(name)
  737. @cached_property
  738. def _cached_var_name(self) -> str:
  739. """The name of the var.
  740. Returns:
  741. The name of the var.
  742. """
  743. return (
  744. "["
  745. + ", ".join(
  746. [str(LiteralVar.create(element)) for element in self._var_value]
  747. )
  748. + "]"
  749. )
  750. @cached_property
  751. def _get_all_var_data(self) -> ImmutableVarData | None:
  752. """Get all VarData associated with the Var.
  753. Returns:
  754. The VarData of the components and all of its children.
  755. """
  756. return ImmutableVarData.merge(
  757. *[
  758. var._get_all_var_data()
  759. for var in self._var_value
  760. if isinstance(var, Var)
  761. ],
  762. self._var_data,
  763. )
  764. type_mapping = {
  765. int: LiteralNumberVar,
  766. float: LiteralNumberVar,
  767. bool: LiteralBooleanVar,
  768. dict: LiteralObjectVar,
  769. list: LiteralArrayVar,
  770. tuple: LiteralArrayVar,
  771. set: LiteralArrayVar,
  772. }