test_var.py 10 KB

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