object.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. """Classes for immutable object vars."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import typing
  5. from inspect import isclass
  6. from typing import (
  7. Any,
  8. List,
  9. Mapping,
  10. NoReturn,
  11. Tuple,
  12. Type,
  13. TypeVar,
  14. Union,
  15. get_args,
  16. overload,
  17. )
  18. from typing_extensions import is_typeddict
  19. from reflex.utils import types
  20. from reflex.utils.exceptions import VarAttributeError
  21. from reflex.utils.types import (
  22. GenericType,
  23. get_attribute_access_type,
  24. get_origin,
  25. safe_issubclass,
  26. )
  27. from .base import (
  28. CachedVarOperation,
  29. LiteralVar,
  30. Var,
  31. VarData,
  32. cached_property_no_lock,
  33. figure_out_type,
  34. var_operation,
  35. var_operation_return,
  36. )
  37. from .number import BooleanVar, NumberVar, raise_unsupported_operand_types
  38. from .sequence import ArrayVar, StringVar
  39. OBJECT_TYPE = TypeVar("OBJECT_TYPE", covariant=True)
  40. KEY_TYPE = TypeVar("KEY_TYPE")
  41. VALUE_TYPE = TypeVar("VALUE_TYPE")
  42. ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
  43. OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
  44. class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
  45. """Base class for immutable object vars."""
  46. def _key_type(self) -> Type:
  47. """Get the type of the keys of the object.
  48. Returns:
  49. The type of the keys of the object.
  50. """
  51. return str
  52. @overload
  53. def _value_type(
  54. self: ObjectVar[Mapping[Any, VALUE_TYPE]],
  55. ) -> Type[VALUE_TYPE]: ...
  56. @overload
  57. def _value_type(self) -> Type: ...
  58. def _value_type(self) -> Type:
  59. """Get the type of the values of the object.
  60. Returns:
  61. The type of the values of the object.
  62. """
  63. fixed_type = get_origin(self._var_type) or self._var_type
  64. if not isclass(fixed_type):
  65. return Any # pyright: ignore [reportReturnType]
  66. args = get_args(self._var_type) if issubclass(fixed_type, Mapping) else ()
  67. return args[1] if args else Any # pyright: ignore [reportReturnType]
  68. def keys(self) -> ArrayVar[List[str]]:
  69. """Get the keys of the object.
  70. Returns:
  71. The keys of the object.
  72. """
  73. return object_keys_operation(self)
  74. @overload
  75. def values(
  76. self: ObjectVar[Mapping[Any, VALUE_TYPE]],
  77. ) -> ArrayVar[List[VALUE_TYPE]]: ...
  78. @overload
  79. def values(self) -> ArrayVar: ...
  80. def values(self) -> ArrayVar:
  81. """Get the values of the object.
  82. Returns:
  83. The values of the object.
  84. """
  85. return object_values_operation(self)
  86. @overload
  87. def entries(
  88. self: ObjectVar[Mapping[Any, VALUE_TYPE]],
  89. ) -> ArrayVar[List[Tuple[str, VALUE_TYPE]]]: ...
  90. @overload
  91. def entries(self) -> ArrayVar: ...
  92. def entries(self) -> ArrayVar:
  93. """Get the entries of the object.
  94. Returns:
  95. The entries of the object.
  96. """
  97. return object_entries_operation(self)
  98. items = entries
  99. def merge(self, other: ObjectVar):
  100. """Merge two objects.
  101. Args:
  102. other: The other object to merge.
  103. Returns:
  104. The merged object.
  105. """
  106. return object_merge_operation(self, other)
  107. # NoReturn is used here to catch when key value is Any
  108. @overload
  109. def __getitem__( # pyright: ignore [reportOverlappingOverload]
  110. self: ObjectVar[Mapping[Any, NoReturn]],
  111. key: Var | Any,
  112. ) -> Var: ...
  113. @overload
  114. def __getitem__(
  115. self: (ObjectVar[Mapping[Any, bool]]),
  116. key: Var | Any,
  117. ) -> BooleanVar: ...
  118. @overload
  119. def __getitem__(
  120. self: (
  121. ObjectVar[Mapping[Any, int]]
  122. | ObjectVar[Mapping[Any, float]]
  123. | ObjectVar[Mapping[Any, int | float]]
  124. ),
  125. key: Var | Any,
  126. ) -> NumberVar: ...
  127. @overload
  128. def __getitem__(
  129. self: ObjectVar[Mapping[Any, str]],
  130. key: Var | Any,
  131. ) -> StringVar: ...
  132. @overload
  133. def __getitem__(
  134. self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]],
  135. key: Var | Any,
  136. ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
  137. @overload
  138. def __getitem__(
  139. self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]],
  140. key: Var | Any,
  141. ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
  142. @overload
  143. def __getitem__(
  144. self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
  145. key: Var | Any,
  146. ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
  147. def __getitem__(self, key: Var | Any) -> Var:
  148. """Get an item from the object.
  149. Args:
  150. key: The key to get from the object.
  151. Returns:
  152. The item from the object.
  153. """
  154. from .sequence import LiteralStringVar
  155. if not isinstance(key, (StringVar, str, int, NumberVar)) or (
  156. isinstance(key, NumberVar) and key._is_strict_float()
  157. ):
  158. raise_unsupported_operand_types("[]", (type(self), type(key)))
  159. if isinstance(key, str) and isinstance(Var.create(key), LiteralStringVar):
  160. return self.__getattr__(key)
  161. return ObjectItemOperation.create(self, key).guess_type()
  162. # NoReturn is used here to catch when key value is Any
  163. @overload
  164. def __getattr__( # pyright: ignore [reportOverlappingOverload]
  165. self: ObjectVar[Mapping[Any, NoReturn]],
  166. name: str,
  167. ) -> Var: ...
  168. @overload
  169. def __getattr__(
  170. self: (
  171. ObjectVar[Mapping[Any, int]]
  172. | ObjectVar[Mapping[Any, float]]
  173. | ObjectVar[Mapping[Any, int | float]]
  174. ),
  175. name: str,
  176. ) -> NumberVar: ...
  177. @overload
  178. def __getattr__(
  179. self: ObjectVar[Mapping[Any, str]],
  180. name: str,
  181. ) -> StringVar: ...
  182. @overload
  183. def __getattr__(
  184. self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]],
  185. name: str,
  186. ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
  187. @overload
  188. def __getattr__(
  189. self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]],
  190. name: str,
  191. ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
  192. @overload
  193. def __getattr__(
  194. self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
  195. name: str,
  196. ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
  197. @overload
  198. def __getattr__(
  199. self: ObjectVar,
  200. name: str,
  201. ) -> ObjectItemOperation: ...
  202. def __getattr__(self, name: str) -> Var:
  203. """Get an attribute of the var.
  204. Args:
  205. name: The name of the attribute.
  206. Raises:
  207. VarAttributeError: The State var has no such attribute or may have been annotated wrongly.
  208. Returns:
  209. The attribute of the var.
  210. """
  211. if name.startswith("__") and name.endswith("__"):
  212. return getattr(super(type(self), self), name)
  213. var_type = self._var_type
  214. if types.is_optional(var_type):
  215. var_type = get_args(var_type)[0]
  216. fixed_type = get_origin(var_type) or var_type
  217. if (
  218. is_typeddict(fixed_type)
  219. or (isclass(fixed_type) and not safe_issubclass(fixed_type, Mapping))
  220. or (fixed_type in types.UnionTypes)
  221. ):
  222. attribute_type = get_attribute_access_type(var_type, name)
  223. if attribute_type is None:
  224. raise VarAttributeError(
  225. f"The State var `{self!s}` has no attribute '{name}' or may have been annotated "
  226. f"wrongly."
  227. )
  228. return ObjectItemOperation.create(self, name, attribute_type).guess_type()
  229. else:
  230. return ObjectItemOperation.create(self, name).guess_type()
  231. def contains(self, key: Var | Any) -> BooleanVar:
  232. """Check if the object contains a key.
  233. Args:
  234. key: The key to check.
  235. Returns:
  236. The result of the check.
  237. """
  238. return object_has_own_property_operation(self, key)
  239. @dataclasses.dataclass(
  240. eq=False,
  241. frozen=True,
  242. slots=True,
  243. )
  244. class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
  245. """Base class for immutable literal object vars."""
  246. _var_value: Mapping[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
  247. default_factory=dict
  248. )
  249. def _key_type(self) -> Type:
  250. """Get the type of the keys of the object.
  251. Returns:
  252. The type of the keys of the object.
  253. """
  254. args_list = typing.get_args(self._var_type)
  255. return args_list[0] if args_list else Any # pyright: ignore [reportReturnType]
  256. def _value_type(self) -> Type:
  257. """Get the type of the values of the object.
  258. Returns:
  259. The type of the values of the object.
  260. """
  261. args_list = typing.get_args(self._var_type)
  262. return args_list[1] if args_list else Any # pyright: ignore [reportReturnType]
  263. @cached_property_no_lock
  264. def _cached_var_name(self) -> str:
  265. """The name of the var.
  266. Returns:
  267. The name of the var.
  268. """
  269. return (
  270. "({ "
  271. + ", ".join(
  272. [
  273. f"[{LiteralVar.create(key)!s}] : {LiteralVar.create(value)!s}"
  274. for key, value in self._var_value.items()
  275. ]
  276. )
  277. + " })"
  278. )
  279. def json(self) -> str:
  280. """Get the JSON representation of the object.
  281. Returns:
  282. The JSON representation of the object.
  283. Raises:
  284. TypeError: The keys and values of the object must be literal vars to get the JSON representation
  285. """
  286. keys_and_values = []
  287. for key, value in self._var_value.items():
  288. key = LiteralVar.create(key)
  289. value = LiteralVar.create(value)
  290. if not isinstance(key, LiteralVar) or not isinstance(value, LiteralVar):
  291. raise TypeError(
  292. "The keys and values of the object must be literal vars to get the JSON representation."
  293. )
  294. keys_and_values.append(f"{key.json()}:{value.json()}")
  295. return "{" + ", ".join(keys_and_values) + "}"
  296. def __hash__(self) -> int:
  297. """Get the hash of the var.
  298. Returns:
  299. The hash of the var.
  300. """
  301. return hash((type(self).__name__, self._js_expr))
  302. @cached_property_no_lock
  303. def _cached_get_all_var_data(self) -> VarData | None:
  304. """Get all the var data.
  305. Returns:
  306. The var data.
  307. """
  308. return VarData.merge(
  309. *[LiteralVar.create(var)._get_all_var_data() for var in self._var_value],
  310. *[
  311. LiteralVar.create(var)._get_all_var_data()
  312. for var in self._var_value.values()
  313. ],
  314. self._var_data,
  315. )
  316. @classmethod
  317. def create(
  318. cls,
  319. _var_value: Mapping,
  320. _var_type: Type[OBJECT_TYPE] | None = None,
  321. _var_data: VarData | None = None,
  322. ) -> LiteralObjectVar[OBJECT_TYPE]:
  323. """Create the literal object var.
  324. Args:
  325. _var_value: The value of the var.
  326. _var_type: The type of the var.
  327. _var_data: Additional hooks and imports associated with the Var.
  328. Returns:
  329. The literal object var.
  330. """
  331. return LiteralObjectVar(
  332. _js_expr="",
  333. _var_type=(figure_out_type(_var_value) if _var_type is None else _var_type),
  334. _var_data=_var_data,
  335. _var_value=_var_value,
  336. )
  337. @var_operation
  338. def object_keys_operation(value: ObjectVar):
  339. """Get the keys of an object.
  340. Args:
  341. value: The object to get the keys from.
  342. Returns:
  343. The keys of the object.
  344. """
  345. return var_operation_return(
  346. js_expression=f"Object.keys({value})",
  347. var_type=List[str],
  348. )
  349. @var_operation
  350. def object_values_operation(value: ObjectVar):
  351. """Get the values of an object.
  352. Args:
  353. value: The object to get the values from.
  354. Returns:
  355. The values of the object.
  356. """
  357. return var_operation_return(
  358. js_expression=f"Object.values({value})",
  359. var_type=List[value._value_type()],
  360. )
  361. @var_operation
  362. def object_entries_operation(value: ObjectVar):
  363. """Get the entries of an object.
  364. Args:
  365. value: The object to get the entries from.
  366. Returns:
  367. The entries of the object.
  368. """
  369. return var_operation_return(
  370. js_expression=f"Object.entries({value})",
  371. var_type=List[Tuple[str, value._value_type()]],
  372. )
  373. @var_operation
  374. def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
  375. """Merge two objects.
  376. Args:
  377. lhs: The first object to merge.
  378. rhs: The second object to merge.
  379. Returns:
  380. The merged object.
  381. """
  382. return var_operation_return(
  383. js_expression=f"({{...{lhs}, ...{rhs}}})",
  384. var_type=Mapping[
  385. Union[lhs._key_type(), rhs._key_type()],
  386. Union[lhs._value_type(), rhs._value_type()],
  387. ],
  388. )
  389. @dataclasses.dataclass(
  390. eq=False,
  391. frozen=True,
  392. slots=True,
  393. )
  394. class ObjectItemOperation(CachedVarOperation, Var):
  395. """Operation to get an item from an object."""
  396. _object: ObjectVar = dataclasses.field(
  397. default_factory=lambda: LiteralObjectVar.create({})
  398. )
  399. _key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
  400. @cached_property_no_lock
  401. def _cached_var_name(self) -> str:
  402. """The name of the operation.
  403. Returns:
  404. The name of the operation.
  405. """
  406. if types.is_optional(self._object._var_type):
  407. return f"{self._object!s}?.[{self._key!s}]"
  408. return f"{self._object!s}[{self._key!s}]"
  409. @classmethod
  410. def create(
  411. cls,
  412. object: ObjectVar,
  413. key: Var | Any,
  414. _var_type: GenericType | None = None,
  415. _var_data: VarData | None = None,
  416. ) -> ObjectItemOperation:
  417. """Create the object item operation.
  418. Args:
  419. object: The object to get the item from.
  420. key: The key to get from the object.
  421. _var_type: The type of the item.
  422. _var_data: Additional hooks and imports associated with the operation.
  423. Returns:
  424. The object item operation.
  425. """
  426. return cls(
  427. _js_expr="",
  428. _var_type=object._value_type() if _var_type is None else _var_type,
  429. _var_data=_var_data,
  430. _object=object,
  431. _key=key if isinstance(key, Var) else LiteralVar.create(key),
  432. )
  433. @var_operation
  434. def object_has_own_property_operation(object: ObjectVar, key: Var):
  435. """Check if an object has a key.
  436. Args:
  437. object: The object to check.
  438. key: The key to check.
  439. Returns:
  440. The result of the check.
  441. """
  442. return var_operation_return(
  443. js_expression=f"{object}.hasOwnProperty({key})",
  444. var_type=bool,
  445. )