base.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. """Collection of base classes."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import sys
  5. from typing import Any, Optional, Type
  6. from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
  7. from reflex.utils import serializers, types
  8. from reflex.utils.exceptions import VarTypeError
  9. from reflex.vars import Var, VarData, _decode_var, _extract_var_data, _global_vars
  10. @dataclasses.dataclass(
  11. eq=False,
  12. frozen=True,
  13. **{"slots": True} if sys.version_info >= (3, 10) else {},
  14. )
  15. class ImmutableVar(Var):
  16. """Base class for immutable vars."""
  17. # The name of the var.
  18. _var_name: str = dataclasses.field()
  19. # The type of the var.
  20. _var_type: Type = dataclasses.field(default=Any)
  21. # Extra metadata associated with the Var
  22. _var_data: Optional[VarData] = dataclasses.field(default=None)
  23. @property
  24. def _var_is_local(self) -> bool:
  25. """Whether this is a local javascript variable.
  26. Returns:
  27. False
  28. """
  29. return False
  30. @property
  31. def _var_is_string(self) -> bool:
  32. """Whether the var is a string literal.
  33. Returns:
  34. False
  35. """
  36. return False
  37. @property
  38. def _var_full_name_needs_state_prefix(self) -> bool:
  39. """Whether the full name of the var needs a _var_state prefix.
  40. Returns:
  41. False
  42. """
  43. return False
  44. def __post_init__(self):
  45. """Post-initialize the var."""
  46. # Decode any inline Var markup and apply it to the instance
  47. _var_data, _var_name = _decode_var(self._var_name)
  48. if _var_data:
  49. self.__init__(
  50. _var_name, self._var_type, VarData.merge(self._var_data, _var_data)
  51. )
  52. def _replace(self, merge_var_data=None, **kwargs: Any):
  53. """Make a copy of this Var with updated fields.
  54. Args:
  55. merge_var_data: VarData to merge into the existing VarData.
  56. **kwargs: Var fields to update.
  57. Returns:
  58. A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
  59. Raises:
  60. TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
  61. """
  62. if kwargs.get("_var_is_local", False) is not False:
  63. raise TypeError(
  64. "The _var_is_local argument is not supported for ImmutableVar."
  65. )
  66. if kwargs.get("_var_is_string", False) is not False:
  67. raise TypeError(
  68. "The _var_is_string argument is not supported for ImmutableVar."
  69. )
  70. if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
  71. raise TypeError(
  72. "The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
  73. )
  74. field_values = dict(
  75. _var_name=kwargs.pop("_var_name", self._var_name),
  76. _var_type=kwargs.pop("_var_type", self._var_type),
  77. _var_data=VarData.merge(
  78. kwargs.get("_var_data", self._var_data), merge_var_data
  79. ),
  80. )
  81. return type(self)(**field_values)
  82. @classmethod
  83. def create(
  84. cls,
  85. value: Any,
  86. _var_is_local: bool | None = None,
  87. _var_is_string: bool | None = None,
  88. _var_data: VarData | None = None,
  89. ) -> Var | None:
  90. """Create a var from a value.
  91. Args:
  92. value: The value to create the var from.
  93. _var_is_local: Whether the var is local. Deprecated.
  94. _var_is_string: Whether the var is a string literal. Deprecated.
  95. _var_data: Additional hooks and imports associated with the Var.
  96. Returns:
  97. The var.
  98. Raises:
  99. VarTypeError: If the value is JSON-unserializable.
  100. TypeError: If _var_is_local or _var_is_string is not None.
  101. """
  102. if _var_is_local is not None:
  103. raise TypeError(
  104. "The _var_is_local argument is not supported for ImmutableVar."
  105. )
  106. if _var_is_string is not None:
  107. raise TypeError(
  108. "The _var_is_string argument is not supported for ImmutableVar."
  109. )
  110. from reflex.utils import format
  111. # Check for none values.
  112. if value is None:
  113. return None
  114. # If the value is already a var, do nothing.
  115. if isinstance(value, Var):
  116. return value
  117. # Try to pull the imports and hooks from contained values.
  118. if not isinstance(value, str):
  119. _var_data = VarData.merge(*_extract_var_data(value), _var_data)
  120. # Try to serialize the value.
  121. type_ = type(value)
  122. if type_ in types.JSONType:
  123. name = value
  124. else:
  125. name, _serialized_type = serializers.serialize(value, get_type=True)
  126. if name is None:
  127. raise VarTypeError(
  128. f"No JSON serializer found for var {value} of type {type_}."
  129. )
  130. name = name if isinstance(name, str) else format.json_dumps(name)
  131. return cls(
  132. _var_name=name,
  133. _var_type=type_,
  134. _var_data=_var_data,
  135. )
  136. @classmethod
  137. def create_safe(
  138. cls,
  139. value: Any,
  140. _var_is_local: bool | None = None,
  141. _var_is_string: bool | None = None,
  142. _var_data: VarData | None = None,
  143. ) -> Var:
  144. """Create a var from a value, asserting that it is not None.
  145. Args:
  146. value: The value to create the var from.
  147. _var_is_local: Whether the var is local. Deprecated.
  148. _var_is_string: Whether the var is a string literal. Deprecated.
  149. _var_data: Additional hooks and imports associated with the Var.
  150. Returns:
  151. The var.
  152. """
  153. var = cls.create(
  154. value,
  155. _var_is_local=_var_is_local,
  156. _var_is_string=_var_is_string,
  157. _var_data=_var_data,
  158. )
  159. assert var is not None
  160. return var
  161. def __format__(self, format_spec: str) -> str:
  162. """Format the var into a Javascript equivalent to an f-string.
  163. Args:
  164. format_spec: The format specifier (Ignored for now).
  165. Returns:
  166. The formatted var.
  167. """
  168. hashed_var = hash(self)
  169. _global_vars[hashed_var] = self
  170. # Encode the _var_data into the formatted output for tracking purposes.
  171. return f"{REFLEX_VAR_OPENING_TAG}{hashed_var}{REFLEX_VAR_CLOSING_TAG}{self._var_name}"
  172. class StringVar(ImmutableVar):
  173. """Base class for immutable string vars."""
  174. class NumberVar(ImmutableVar):
  175. """Base class for immutable number vars."""
  176. class BooleanVar(ImmutableVar):
  177. """Base class for immutable boolean vars."""
  178. class ObjectVar(ImmutableVar):
  179. """Base class for immutable object vars."""
  180. class ArrayVar(ImmutableVar):
  181. """Base class for immutable array vars."""
  182. class FunctionVar(ImmutableVar):
  183. """Base class for immutable function vars."""