test_var.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. import typing
  2. from typing import Dict, List
  3. import cloudpickle
  4. import pytest
  5. from pynecone.base import Base
  6. from pynecone.state import State
  7. from pynecone.vars import (
  8. BaseVar,
  9. ComputedVar,
  10. ImportVar,
  11. PCDict,
  12. PCList,
  13. Var,
  14. get_local_storage,
  15. )
  16. test_vars = [
  17. BaseVar(name="prop1", type_=int),
  18. BaseVar(name="key", type_=str),
  19. BaseVar(name="value", type_=str, state="state"),
  20. BaseVar(name="local", type_=str, state="state", is_local=True),
  21. BaseVar(name="local2", type_=str, is_local=True),
  22. ]
  23. test_import_vars = [ImportVar(tag="DataGrid"), ImportVar(tag="DataGrid", alias="Grid")]
  24. class BaseState(State):
  25. """A Test State."""
  26. val: str = "key"
  27. @pytest.fixture
  28. def TestObj():
  29. class TestObj(Base):
  30. foo: int
  31. bar: str
  32. return TestObj
  33. @pytest.fixture
  34. def ParentState(TestObj):
  35. class ParentState(State):
  36. foo: int
  37. bar: int
  38. @ComputedVar
  39. def var_without_annotation(self):
  40. return TestObj
  41. return ParentState
  42. @pytest.fixture
  43. def ChildState(ParentState, TestObj):
  44. class ChildState(ParentState):
  45. @ComputedVar
  46. def var_without_annotation(self):
  47. return TestObj
  48. return ChildState
  49. @pytest.fixture
  50. def GrandChildState(ChildState, TestObj):
  51. class GrandChildState(ChildState):
  52. @ComputedVar
  53. def var_without_annotation(self):
  54. return TestObj
  55. return GrandChildState
  56. @pytest.fixture
  57. def StateWithAnyVar(TestObj):
  58. class StateWithAnyVar(State):
  59. @ComputedVar
  60. def var_without_annotation(self) -> typing.Any:
  61. return TestObj
  62. return StateWithAnyVar
  63. @pytest.fixture
  64. def StateWithCorrectVarAnnotation():
  65. class StateWithCorrectVarAnnotation(State):
  66. @ComputedVar
  67. def var_with_annotation(self) -> str:
  68. return "Correct annotation"
  69. return StateWithCorrectVarAnnotation
  70. @pytest.fixture
  71. def StateWithWrongVarAnnotation(TestObj):
  72. class StateWithWrongVarAnnotation(State):
  73. @ComputedVar
  74. def var_with_annotation(self) -> str:
  75. return TestObj
  76. return StateWithWrongVarAnnotation
  77. @pytest.mark.parametrize(
  78. "prop,expected",
  79. zip(
  80. test_vars,
  81. [
  82. "prop1",
  83. "key",
  84. "state.value",
  85. "state.local",
  86. "local2",
  87. ],
  88. ),
  89. )
  90. def test_full_name(prop, expected):
  91. """Test that the full name of a var is correct.
  92. Args:
  93. prop: The var to test.
  94. expected: The expected full name.
  95. """
  96. assert prop.full_name == expected
  97. @pytest.mark.parametrize(
  98. "prop,expected",
  99. zip(
  100. test_vars,
  101. ["{prop1}", "{key}", "{state.value}", "state.local", "local2"],
  102. ),
  103. )
  104. def test_str(prop, expected):
  105. """Test that the string representation of a var is correct.
  106. Args:
  107. prop: The var to test.
  108. expected: The expected string representation.
  109. """
  110. assert str(prop) == expected
  111. @pytest.mark.parametrize(
  112. "prop,expected",
  113. [
  114. (BaseVar(name="p", type_=int), 0),
  115. (BaseVar(name="p", type_=float), 0.0),
  116. (BaseVar(name="p", type_=str), ""),
  117. (BaseVar(name="p", type_=bool), False),
  118. (BaseVar(name="p", type_=list), []),
  119. (BaseVar(name="p", type_=dict), {}),
  120. (BaseVar(name="p", type_=tuple), ()),
  121. (BaseVar(name="p", type_=set), set()),
  122. ],
  123. )
  124. def test_default_value(prop, expected):
  125. """Test that the default value of a var is correct.
  126. Args:
  127. prop: The var to test.
  128. expected: The expected default value.
  129. """
  130. assert prop.get_default_value() == expected
  131. @pytest.mark.parametrize(
  132. "prop,expected",
  133. zip(
  134. test_vars,
  135. [
  136. "set_prop1",
  137. "set_key",
  138. "state.set_value",
  139. "state.set_local",
  140. "set_local2",
  141. ],
  142. ),
  143. )
  144. def test_get_setter(prop, expected):
  145. """Test that the name of the setter function of a var is correct.
  146. Args:
  147. prop: The var to test.
  148. expected: The expected name of the setter function.
  149. """
  150. assert prop.get_setter_name() == expected
  151. @pytest.mark.parametrize(
  152. "value,expected",
  153. [
  154. (None, None),
  155. (1, BaseVar(name="1", type_=int, is_local=True)),
  156. ("key", BaseVar(name="key", type_=str, is_local=True)),
  157. (3.14, BaseVar(name="3.14", type_=float, is_local=True)),
  158. ([1, 2, 3], BaseVar(name="[1, 2, 3]", type_=list, is_local=True)),
  159. (
  160. {"a": 1, "b": 2},
  161. BaseVar(name='{"a": 1, "b": 2}', type_=dict, is_local=True),
  162. ),
  163. ],
  164. )
  165. def test_create(value, expected):
  166. """Test the var create function.
  167. Args:
  168. value: The value to create a var from.
  169. expected: The expected name of the setter function.
  170. """
  171. prop = Var.create(value)
  172. if value is None:
  173. assert prop == expected
  174. else:
  175. assert prop.equals(expected) # type: ignore
  176. def test_create_type_error():
  177. """Test the var create function when inputs type error."""
  178. class ErrorType:
  179. pass
  180. value = ErrorType()
  181. with pytest.raises(TypeError) as exception:
  182. Var.create(value)
  183. assert (
  184. exception.value.args[0]
  185. == f"To create a Var must be Var or JSON-serializable. Got {value} of type {type(value)}."
  186. )
  187. def v(value) -> Var:
  188. val = Var.create(value)
  189. assert val is not None
  190. return val
  191. def test_basic_operations(TestObj):
  192. """Test the var operations.
  193. Args:
  194. TestObj: The test object.
  195. """
  196. assert str(v(1) == v(2)) == "{(1 === 2)}"
  197. assert str(v(1) != v(2)) == "{(1 !== 2)}"
  198. assert str(v(1) < v(2)) == "{(1 < 2)}"
  199. assert str(v(1) <= v(2)) == "{(1 <= 2)}"
  200. assert str(v(1) > v(2)) == "{(1 > 2)}"
  201. assert str(v(1) >= v(2)) == "{(1 >= 2)}"
  202. assert str(v(1) + v(2)) == "{(1 + 2)}"
  203. assert str(v(1) - v(2)) == "{(1 - 2)}"
  204. assert str(v(1) * v(2)) == "{(1 * 2)}"
  205. assert str(v(1) / v(2)) == "{(1 / 2)}"
  206. assert str(v(1) // v(2)) == "{Math.floor(1 / 2)}"
  207. assert str(v(1) % v(2)) == "{(1 % 2)}"
  208. assert str(v(1) ** v(2)) == "{Math.pow(1 , 2)}"
  209. assert str(v(1) & v(2)) == "{(1 && 2)}"
  210. assert str(v(1) | v(2)) == "{(1 || 2)}"
  211. assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}"
  212. assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}'
  213. assert (
  214. str(BaseVar(name="foo", state="state", type_=TestObj).bar) == "{state.foo.bar}"
  215. )
  216. assert str(abs(v(1))) == "{Math.abs(1)}"
  217. assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
  218. def test_var_indexing_lists():
  219. """Test that we can index into list vars."""
  220. lst = BaseVar(name="lst", type_=List[int])
  221. # Test basic indexing.
  222. assert str(lst[0]) == "{lst.at(0)}"
  223. assert str(lst[1]) == "{lst.at(1)}"
  224. # Test negative indexing.
  225. assert str(lst[-1]) == "{lst.at(-1)}"
  226. # Test non-integer indexing raises an error.
  227. with pytest.raises(TypeError):
  228. lst["a"]
  229. with pytest.raises(TypeError):
  230. lst[1.5]
  231. def test_var_list_slicing():
  232. """Test that we can slice into list vars."""
  233. lst = BaseVar(name="lst", type_=List[int])
  234. assert str(lst[:1]) == "{lst.slice(0, 1)}"
  235. assert str(lst[:1]) == "{lst.slice(0, 1)}"
  236. assert str(lst[:]) == "{lst.slice(0, undefined)}"
  237. def test_dict_indexing():
  238. """Test that we can index into dict vars."""
  239. dct = BaseVar(name="dct", type_=Dict[str, int])
  240. # Check correct indexing.
  241. assert str(dct["a"]) == '{dct["a"]}'
  242. assert str(dct["asdf"]) == '{dct["asdf"]}'
  243. @pytest.mark.parametrize(
  244. "fixture,full_name",
  245. [
  246. ("ParentState", "parent_state.var_without_annotation"),
  247. ("ChildState", "parent_state.child_state.var_without_annotation"),
  248. (
  249. "GrandChildState",
  250. "parent_state.child_state.grand_child_state.var_without_annotation",
  251. ),
  252. ("StateWithAnyVar", "state_with_any_var.var_without_annotation"),
  253. ],
  254. )
  255. def test_computed_var_without_annotation_error(request, fixture, full_name):
  256. """Test that a type error is thrown when an attribute of a computed var is
  257. accessed without annotating the computed var.
  258. Args:
  259. request: Fixture Request.
  260. fixture: The state fixture.
  261. full_name: The full name of the state var.
  262. """
  263. with pytest.raises(TypeError) as err:
  264. state = request.getfixturevalue(fixture)
  265. state.var_without_annotation.foo
  266. assert (
  267. err.value.args[0]
  268. == f"You must provide an annotation for the state var `{full_name}`. Annotation cannot be `typing.Any`"
  269. )
  270. @pytest.mark.parametrize(
  271. "fixture,full_name",
  272. [
  273. (
  274. "StateWithCorrectVarAnnotation",
  275. "state_with_correct_var_annotation.var_with_annotation",
  276. ),
  277. (
  278. "StateWithWrongVarAnnotation",
  279. "state_with_wrong_var_annotation.var_with_annotation",
  280. ),
  281. ],
  282. )
  283. def test_computed_var_with_annotation_error(request, fixture, full_name):
  284. """Test that an Attribute error is thrown when a non-existent attribute of an annotated computed var is
  285. accessed or when the wrong annotation is provided to a computed var.
  286. Args:
  287. request: Fixture Request.
  288. fixture: The state fixture.
  289. full_name: The full name of the state var.
  290. """
  291. with pytest.raises(AttributeError) as err:
  292. state = request.getfixturevalue(fixture)
  293. state.var_with_annotation.foo
  294. assert (
  295. err.value.args[0]
  296. == f"The State var `{full_name}` has no attribute 'foo' or may have been annotated wrongly.\n"
  297. f"original message: 'ComputedVar' object has no attribute 'foo'"
  298. )
  299. def test_pickleable_pc_list():
  300. """Test that PCList is pickleable."""
  301. pc_list = PCList(
  302. original_list=[1, 2, 3], reassign_field=lambda x: x, field_name="random"
  303. )
  304. pickled_list = cloudpickle.dumps(pc_list)
  305. assert cloudpickle.loads(pickled_list) == pc_list
  306. def test_pickleable_pc_dict():
  307. """Test that PCDict is pickleable."""
  308. pc_dict = PCDict(
  309. original_dict={1: 2, 3: 4}, reassign_field=lambda x: x, field_name="random"
  310. )
  311. pickled_dict = cloudpickle.dumps(pc_dict)
  312. assert cloudpickle.loads(pickled_dict) == pc_dict
  313. @pytest.mark.parametrize(
  314. "import_var,expected",
  315. zip(
  316. test_import_vars,
  317. [
  318. "DataGrid",
  319. "DataGrid as Grid",
  320. ],
  321. ),
  322. )
  323. def test_import_var(import_var, expected):
  324. """Test that the import var name is computed correctly.
  325. Args:
  326. import_var: The import var.
  327. expected: expected name
  328. """
  329. assert import_var.name == expected
  330. @pytest.mark.parametrize(
  331. "key, expected",
  332. [
  333. ("test_key", BaseVar(name="localStorage.getItem('test_key')", type_=str)),
  334. (
  335. BaseVar(name="key_var", type_=str),
  336. BaseVar(name="localStorage.getItem(key_var)", type_=str),
  337. ),
  338. (
  339. BaseState.val,
  340. BaseVar(name="localStorage.getItem(base_state.val)", type_=str),
  341. ),
  342. (None, BaseVar(name="getAllLocalStorageItems()", type_=Dict)),
  343. ],
  344. )
  345. def test_get_local_storage(key, expected):
  346. """Test that the right BaseVar is return when get_local_storage is called.
  347. Args:
  348. key: Local storage key.
  349. expected: expected BaseVar.
  350. """
  351. local_storage = get_local_storage(key)
  352. assert local_storage.name == expected.name
  353. assert local_storage.type_ == expected.type_
  354. @pytest.mark.parametrize(
  355. "key",
  356. [
  357. ["list", "values"],
  358. {"name": "dict"},
  359. 10,
  360. BaseVar(name="key_var", type_=List),
  361. BaseVar(name="key_var", type_=Dict[str, str]),
  362. ],
  363. )
  364. def test_get_local_storage_raise_error(key):
  365. """Test that a type error is thrown when the wrong key type is provided.
  366. Args:
  367. key: Local storage key.
  368. """
  369. with pytest.raises(TypeError) as err:
  370. get_local_storage(key)
  371. type_ = type(key) if not isinstance(key, Var) else key.type_
  372. assert (
  373. err.value.args[0]
  374. == f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead."
  375. )