object.py 17 KB

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