object.py 15 KB

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