Преглед изворни кода

Var: __bool__ and __iter__ always raise a TypeError (#1750)

Masen Furer пре 1 година
родитељ
комит
71811a600c

+ 2 - 2
reflex/.templates/jinja/web/pages/utils.js.jinja2

@@ -6,9 +6,9 @@
 {% filter indent(width=indent_width) %}
 {% filter indent(width=indent_width) %}
   {%- if component is not mapping %}
   {%- if component is not mapping %}
     {{- component }}
     {{- component }}
-  {%- elif component.iterable %}
+  {%- elif "iterable" in component %}
     {{- render_iterable_tag(component) }}
     {{- render_iterable_tag(component) }}
-  {%- elif component.cond %}
+  {%- elif "cond" in component %}
     {{- render_condition_tag(component) }}
     {{- render_condition_tag(component) }}
   {%- elif component.children|length %}
   {%- elif component.children|length %}
     {{- render_tag(component) }}
     {{- render_tag(component) }}

+ 7 - 3
reflex/components/datadisplay/datatable.py

@@ -66,7 +66,11 @@ class DataTable(Gridjs):
                 "Annotation of the computed var assigned to the data field should be provided."
                 "Annotation of the computed var assigned to the data field should be provided."
             )
             )
 
 
-        if columns and isinstance(columns, ComputedVar) and columns.type_ == Any:
+        if (
+            columns is not None
+            and isinstance(columns, ComputedVar)
+            and columns.type_ == Any
+        ):
             raise ValueError(
             raise ValueError(
                 "Annotation of the computed var assigned to the column field should be provided."
                 "Annotation of the computed var assigned to the column field should be provided."
             )
             )
@@ -75,7 +79,7 @@ class DataTable(Gridjs):
         if (
         if (
             types.is_dataframe(type(data))
             types.is_dataframe(type(data))
             or (isinstance(data, Var) and types.is_dataframe(data.type_))
             or (isinstance(data, Var) and types.is_dataframe(data.type_))
-        ) and props.get("columns"):
+        ) and columns is not None:
             raise ValueError(
             raise ValueError(
                 "Cannot pass in both a pandas dataframe and columns to the data_table component."
                 "Cannot pass in both a pandas dataframe and columns to the data_table component."
             )
             )
@@ -84,7 +88,7 @@ class DataTable(Gridjs):
         if (
         if (
             (isinstance(data, Var) and types._issubclass(data.type_, List))
             (isinstance(data, Var) and types._issubclass(data.type_, List))
             or issubclass(type(data), List)
             or issubclass(type(data), List)
-        ) and not props.get("columns"):
+        ) and columns is None:
             raise ValueError(
             raise ValueError(
                 "column field should be specified when the data field is a list type"
                 "column field should be specified when the data field is a list type"
             )
             )

+ 1 - 1
reflex/components/navigation/link.py

@@ -45,7 +45,7 @@ class Link(ChakraComponent):
         Returns:
         Returns:
             Component: The link component
             Component: The link component
         """
         """
-        if props.get("href"):
+        if props.get("href") is not None:
             if not len(children):
             if not len(children):
                 raise ValueError("Link without a child will not display")
                 raise ValueError("Link without a child will not display")
         else:
         else:

+ 1 - 1
reflex/state.py

@@ -885,7 +885,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
                 self.dirty_vars.add(cvar)
                 self.dirty_vars.add(cvar)
                 dirty_vars.add(cvar)
                 dirty_vars.add(cvar)
                 actual_var = self.computed_vars.get(cvar)
                 actual_var = self.computed_vars.get(cvar)
-                if actual_var:
+                if actual_var is not None:
                     actual_var.mark_dirty(instance=self)
                     actual_var.mark_dirty(instance=self)
 
 
     def _dirty_computed_vars(self, from_vars: set[str] | None = None) -> set[str]:
     def _dirty_computed_vars(self, from_vars: set[str] | None = None) -> set[str]:

+ 3 - 4
reflex/utils/format.py

@@ -542,7 +542,7 @@ def format_ref(ref: str) -> str:
     return f"ref_{clean_ref}"
     return f"ref_{clean_ref}"
 
 
 
 
-def format_array_ref(refs: str, idx) -> str:
+def format_array_ref(refs: str, idx: Var | None) -> str:
     """Format a ref accessed by array.
     """Format a ref accessed by array.
 
 
     Args:
     Args:
@@ -553,11 +553,10 @@ def format_array_ref(refs: str, idx) -> str:
         The formatted ref.
         The formatted ref.
     """
     """
     clean_ref = re.sub(r"[^\w]+", "_", refs)
     clean_ref = re.sub(r"[^\w]+", "_", refs)
-    if idx:
+    if idx is not None:
         idx.is_local = True
         idx.is_local = True
         return f"refs_{clean_ref}[{idx}]"
         return f"refs_{clean_ref}[{idx}]"
-    else:
-        return f"refs_{clean_ref}"
+    return f"refs_{clean_ref}"
 
 
 
 
 def format_dict(prop: ComponentStyle) -> str:
 def format_dict(prop: ComponentStyle) -> str:

+ 22 - 1
reflex/vars.py

@@ -205,6 +205,27 @@ class Var(ABC):
             out = format.format_string(out)
             out = format.format_string(out)
         return out
         return out
 
 
+    def __bool__(self) -> bool:
+        """Raise exception if using Var in a boolean context.
+
+        Raises:
+            TypeError: when attempting to bool-ify the Var.
+        """
+        raise TypeError(
+            f"Cannot convert Var {self.full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
+            "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
+        )
+
+    def __iter__(self) -> Any:
+        """Raise exception if using Var in an iterable context.
+
+        Raises:
+            TypeError: when attempting to iterate over the Var.
+        """
+        raise TypeError(
+            f"Cannot iterate over Var {self.full_name!r}. Instead use `rx.foreach`."
+        )
+
     def __format__(self, format_spec: str) -> str:
     def __format__(self, format_spec: str) -> str:
         """Format the var into a Javascript equivalent to an f-string.
         """Format the var into a Javascript equivalent to an f-string.
 
 
@@ -1414,7 +1435,7 @@ def get_local_storage(key: Var | str | None = None) -> BaseVar:
     Raises:
     Raises:
         TypeError:  if the wrong key type is provided.
         TypeError:  if the wrong key type is provided.
     """
     """
-    if key:
+    if key is not None:
         if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str):
         if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str):
             type_ = type(key) if not isinstance(key, Var) else key.type_
             type_ = type(key) if not isinstance(key, Var) else key.type_
             raise TypeError(
             raise TypeError(

+ 5 - 4
tests/components/forms/test_debounce.py

@@ -50,8 +50,8 @@ def test_render_child_props():
         )
         )
     )._render()
     )._render()
     assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
     assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
-    assert tag.props["value"] == BaseVar(
-        name="real", type_=str, is_local=True, is_string=False
+    assert tag.props["value"].equals(
+        BaseVar(name="real", type_=str, is_local=True, is_string=False)
     )
     )
     assert len(tag.props["onChange"].events) == 1
     assert len(tag.props["onChange"].events) == 1
     assert tag.props["onChange"].events[0].handler == S.on_change
     assert tag.props["onChange"].events[0].handler == S.on_change
@@ -75,6 +75,7 @@ def test_render_child_props_recursive():
                         on_change=S.on_change,
                         on_change=S.on_change,
                     ),
                     ),
                     value="inner",
                     value="inner",
+                    debounce_timeout=666,
                     force_notify_on_blur=False,
                     force_notify_on_blur=False,
                 ),
                 ),
                 debounce_timeout=42,
                 debounce_timeout=42,
@@ -84,8 +85,8 @@ def test_render_child_props_recursive():
         force_notify_by_enter=False,
         force_notify_by_enter=False,
     )._render()
     )._render()
     assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
     assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
-    assert tag.props["value"] == BaseVar(
-        name="real", type_=str, is_local=True, is_string=False
+    assert tag.props["value"].equals(
+        BaseVar(name="outer", type_=str, is_local=True, is_string=False)
     )
     )
     assert tag.props["forceNotifyOnBlur"].name == "false"
     assert tag.props["forceNotifyOnBlur"].name == "false"
     assert tag.props["forceNotifyByEnter"].name == "false"
     assert tag.props["forceNotifyByEnter"].name == "false"

+ 1 - 1
tests/components/layout/test_cond.py

@@ -104,7 +104,7 @@ def test_cond_no_else():
     assert isinstance(comp, Fragment)
     assert isinstance(comp, Fragment)
     comp = comp.children[0]
     comp = comp.children[0]
     assert isinstance(comp, Cond)
     assert isinstance(comp, Cond)
-    assert comp.cond == True  # noqa
+    assert comp.cond._decode() is True  # type: ignore
     assert comp.comp1 == Fragment.create(Text.create("hello"))
     assert comp.comp1 == Fragment.create(Text.create("hello"))
     assert comp.comp2 == Fragment.create()
     assert comp.comp2 == Fragment.create()
 
 

+ 1 - 1
tests/components/layout/test_foreach.py

@@ -186,6 +186,6 @@ def test_foreach_render(state_var, render_fn, render_dict):
     rend = component.render()
     rend = component.render()
     arg_index = rend["arg_index"]
     arg_index = rend["arg_index"]
     assert rend["iterable_state"] == render_dict["iterable_state"]
     assert rend["iterable_state"] == render_dict["iterable_state"]
-    assert rend["arg_index"] == render_dict["arg_index"]
+    assert arg_index.name == render_dict["arg_index"]
     assert arg_index.type_ == int
     assert arg_index.type_ == int
     assert rend["iterable_type"] == render_dict["iterable_type"]
     assert rend["iterable_type"] == render_dict["iterable_type"]

+ 3 - 3
tests/components/test_component.py

@@ -329,8 +329,8 @@ def test_valid_props(component1, text: str, number: int):
         number: A test number.
         number: A test number.
     """
     """
     c = component1.create(text=text, number=number)
     c = component1.create(text=text, number=number)
-    assert c.text == text
-    assert c.number == number
+    assert c.text._decode() == text
+    assert c.number._decode() == number
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
@@ -357,7 +357,7 @@ def test_var_props(component1, test_state):
         test_state: A test state.
         test_state: A test state.
     """
     """
     c1 = component1.create(text="hello", number=test_state.num)
     c1 = component1.create(text="hello", number=test_state.num)
-    assert c1.number == test_state.num
+    assert c1.number.equals(test_state.num)
 
 
 
 
 def test_get_controlled_triggers(component1, component2):
 def test_get_controlled_triggers(component1, component2):

+ 6 - 4
tests/components/test_tag.py

@@ -52,8 +52,8 @@ def test_is_valid_prop(prop: Var, valid: bool):
 def test_add_props():
 def test_add_props():
     """Test that the props are added."""
     """Test that the props are added."""
     tag = Tag().add_props(key="value", key2=42, invalid=None, invalid2={})
     tag = Tag().add_props(key="value", key2=42, invalid=None, invalid2={})
-    assert tag.props["key"] == Var.create("value")
-    assert tag.props["key2"] == Var.create(42)
+    assert tag.props["key"].equals(Var.create("value"))
+    assert tag.props["key2"].equals(Var.create(42))
     assert "invalid" not in tag.props
     assert "invalid" not in tag.props
     assert "invalid2" not in tag.props
     assert "invalid2" not in tag.props
 
 
@@ -100,7 +100,8 @@ def test_format_tag(tag: Tag, expected: Dict):
     tag_dict = dict(tag)
     tag_dict = dict(tag)
     assert tag_dict["name"] == expected["name"]
     assert tag_dict["name"] == expected["name"]
     assert tag_dict["contents"] == expected["contents"]
     assert tag_dict["contents"] == expected["contents"]
-    assert tag_dict["props"] == expected["props"]
+    for prop, prop_value in tag_dict["props"].items():
+        assert prop_value.equals(Var.create_safe(expected["props"][prop]))
 
 
 
 
 def test_format_cond_tag():
 def test_format_cond_tag():
@@ -116,7 +117,8 @@ def test_format_cond_tag():
         tag_dict["true_value"],
         tag_dict["true_value"],
         tag_dict["false_value"],
         tag_dict["false_value"],
     )
     )
-    assert cond == "logged_in"
+    assert cond.name == "logged_in"
+    assert cond.type_ == bool
 
 
     assert true_value["name"] == "h1"
     assert true_value["name"] == "h1"
     assert true_value["contents"] == "True content"
     assert true_value["contents"] == "True content"

+ 2 - 1
tests/test_app.py

@@ -930,5 +930,6 @@ async def test_process_events(gen_state, mocker):
 
 
     async for _update in process(app, event, "mock_sid", {}, "127.0.0.1"):
     async for _update in process(app, event, "mock_sid", {}, "127.0.0.1"):
         pass
         pass
-    assert gen_state.value == 5
+
+    assert app.state_manager.get_state("token").value == 5
     assert app.postprocess.call_count == 6
     assert app.postprocess.call_count == 6

+ 38 - 24
tests/test_event.py

@@ -55,7 +55,10 @@ def test_call_event_handler():
 
 
     # Test passing vars as args.
     # Test passing vars as args.
     assert event_spec.handler == handler
     assert event_spec.handler == handler
-    assert event_spec.args == (("arg1", "first"), ("arg2", "second"))
+    assert event_spec.args[0][0].equals(Var.create_safe("arg1"))
+    assert event_spec.args[0][1].equals(Var.create_safe("first"))
+    assert event_spec.args[1][0].equals(Var.create_safe("arg2"))
+    assert event_spec.args[1][1].equals(Var.create_safe("second"))
     assert (
     assert (
         format.format_event(event_spec)
         format.format_event(event_spec)
         == 'E("test_fn_with_args", {arg1:first,arg2:second})'
         == 'E("test_fn_with_args", {arg1:first,arg2:second})'
@@ -77,10 +80,10 @@ def test_call_event_handler():
     )
     )
 
 
     assert event_spec.handler == handler
     assert event_spec.handler == handler
-    assert event_spec.args == (
-        ("arg1", format.json_dumps(first)),
-        ("arg2", format.json_dumps(second)),
-    )
+    assert event_spec.args[0][0].equals(Var.create_safe("arg1"))
+    assert event_spec.args[0][1].equals(Var.create_safe(first))
+    assert event_spec.args[1][0].equals(Var.create_safe("arg2"))
+    assert event_spec.args[1][1].equals(Var.create_safe(second))
 
 
     handler = EventHandler(fn=test_fn_with_args)
     handler = EventHandler(fn=test_fn_with_args)
     with pytest.raises(TypeError):
     with pytest.raises(TypeError):
@@ -121,7 +124,8 @@ def test_event_redirect():
     spec = event.redirect("/path")
     spec = event.redirect("/path")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_redirect"
     assert spec.handler.fn.__qualname__ == "_redirect"
-    assert spec.args == (("path", "/path"),)
+    assert spec.args[0][0].equals(Var.create_safe("path"))
+    assert spec.args[0][1].equals(Var.create_safe("/path"))
     assert format.format_event(spec) == 'E("_redirect", {path:"/path"})'
     assert format.format_event(spec) == 'E("_redirect", {path:"/path"})'
     spec = event.redirect(Var.create_safe("path"))
     spec = event.redirect(Var.create_safe("path"))
     assert format.format_event(spec) == 'E("_redirect", {path:path})'
     assert format.format_event(spec) == 'E("_redirect", {path:path})'
@@ -132,7 +136,8 @@ def test_event_console_log():
     spec = event.console_log("message")
     spec = event.console_log("message")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_console"
     assert spec.handler.fn.__qualname__ == "_console"
-    assert spec.args == (("message", "message"),)
+    assert spec.args[0][0].equals(Var.create_safe("message"))
+    assert spec.args[0][1].equals(Var.create_safe("message"))
     assert format.format_event(spec) == 'E("_console", {message:"message"})'
     assert format.format_event(spec) == 'E("_console", {message:"message"})'
     spec = event.console_log(Var.create_safe("message"))
     spec = event.console_log(Var.create_safe("message"))
     assert format.format_event(spec) == 'E("_console", {message:message})'
     assert format.format_event(spec) == 'E("_console", {message:message})'
@@ -143,7 +148,8 @@ def test_event_window_alert():
     spec = event.window_alert("message")
     spec = event.window_alert("message")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_alert"
     assert spec.handler.fn.__qualname__ == "_alert"
-    assert spec.args == (("message", "message"),)
+    assert spec.args[0][0].equals(Var.create_safe("message"))
+    assert spec.args[0][1].equals(Var.create_safe("message"))
     assert format.format_event(spec) == 'E("_alert", {message:"message"})'
     assert format.format_event(spec) == 'E("_alert", {message:"message"})'
     spec = event.window_alert(Var.create_safe("message"))
     spec = event.window_alert(Var.create_safe("message"))
     assert format.format_event(spec) == 'E("_alert", {message:message})'
     assert format.format_event(spec) == 'E("_alert", {message:message})'
@@ -154,7 +160,8 @@ def test_set_focus():
     spec = event.set_focus("input1")
     spec = event.set_focus("input1")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_set_focus"
     assert spec.handler.fn.__qualname__ == "_set_focus"
-    assert spec.args == (("ref", Var.create_safe("ref_input1")),)
+    assert spec.args[0][0].equals(Var.create_safe("ref"))
+    assert spec.args[0][1].equals(Var.create_safe("ref_input1"))
     assert format.format_event(spec) == 'E("_set_focus", {ref:ref_input1})'
     assert format.format_event(spec) == 'E("_set_focus", {ref:ref_input1})'
     spec = event.set_focus("input1")
     spec = event.set_focus("input1")
     assert format.format_event(spec) == 'E("_set_focus", {ref:ref_input1})'
     assert format.format_event(spec) == 'E("_set_focus", {ref:ref_input1})'
@@ -165,10 +172,10 @@ def test_set_value():
     spec = event.set_value("input1", "")
     spec = event.set_value("input1", "")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_set_value"
     assert spec.handler.fn.__qualname__ == "_set_value"
-    assert spec.args == (
-        ("ref", Var.create_safe("ref_input1")),
-        ("value", ""),
-    )
+    assert spec.args[0][0].equals(Var.create_safe("ref"))
+    assert spec.args[0][1].equals(Var.create_safe("ref_input1"))
+    assert spec.args[1][0].equals(Var.create_safe("value"))
+    assert spec.args[1][1].equals(Var.create_safe(""))
     assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""})'
     assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""})'
     spec = event.set_value("input1", Var.create_safe("message"))
     spec = event.set_value("input1", Var.create_safe("message"))
     assert (
     assert (
@@ -181,10 +188,10 @@ def test_set_cookie():
     spec = event.set_cookie("testkey", "testvalue")
     spec = event.set_cookie("testkey", "testvalue")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_set_cookie"
     assert spec.handler.fn.__qualname__ == "_set_cookie"
-    assert spec.args == (
-        ("key", "testkey"),
-        ("value", "testvalue"),
-    )
+    assert spec.args[0][0].equals(Var.create_safe("key"))
+    assert spec.args[0][1].equals(Var.create_safe("testkey"))
+    assert spec.args[1][0].equals(Var.create_safe("value"))
+    assert spec.args[1][1].equals(Var.create_safe("testvalue"))
     assert (
     assert (
         format.format_event(spec)
         format.format_event(spec)
         == 'E("_set_cookie", {key:"testkey",value:"testvalue"})'
         == 'E("_set_cookie", {key:"testkey",value:"testvalue"})'
@@ -196,7 +203,10 @@ def test_remove_cookie():
     spec = event.remove_cookie("testkey")
     spec = event.remove_cookie("testkey")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_remove_cookie"
     assert spec.handler.fn.__qualname__ == "_remove_cookie"
-    assert spec.args == (("key", "testkey"), ("options", {}))
+    assert spec.args[0][0].equals(Var.create_safe("key"))
+    assert spec.args[0][1].equals(Var.create_safe("testkey"))
+    assert spec.args[1][0].equals(Var.create_safe("options"))
+    assert spec.args[1][1].equals(Var.create_safe({}))
     assert (
     assert (
         format.format_event(spec) == 'E("_remove_cookie", {key:"testkey",options:{}})'
         format.format_event(spec) == 'E("_remove_cookie", {key:"testkey",options:{}})'
     )
     )
@@ -213,7 +223,10 @@ def test_remove_cookie_with_options():
     spec = event.remove_cookie("testkey", options)
     spec = event.remove_cookie("testkey", options)
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_remove_cookie"
     assert spec.handler.fn.__qualname__ == "_remove_cookie"
-    assert spec.args == (("key", "testkey"), ("options", options))
+    assert spec.args[0][0].equals(Var.create_safe("key"))
+    assert spec.args[0][1].equals(Var.create_safe("testkey"))
+    assert spec.args[1][0].equals(Var.create_safe("options"))
+    assert spec.args[1][1].equals(Var.create_safe(options))
     assert (
     assert (
         format.format_event(spec)
         format.format_event(spec)
         == f'E("_remove_cookie", {{key:"testkey",options:{json.dumps(options)}}})'
         == f'E("_remove_cookie", {{key:"testkey",options:{json.dumps(options)}}})'
@@ -225,10 +238,10 @@ def test_set_local_storage():
     spec = event.set_local_storage("testkey", "testvalue")
     spec = event.set_local_storage("testkey", "testvalue")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_set_local_storage"
     assert spec.handler.fn.__qualname__ == "_set_local_storage"
-    assert spec.args == (
-        ("key", "testkey"),
-        ("value", "testvalue"),
-    )
+    assert spec.args[0][0].equals(Var.create_safe("key"))
+    assert spec.args[0][1].equals(Var.create_safe("testkey"))
+    assert spec.args[1][0].equals(Var.create_safe("value"))
+    assert spec.args[1][1].equals(Var.create_safe("testvalue"))
     assert (
     assert (
         format.format_event(spec)
         format.format_event(spec)
         == 'E("_set_local_storage", {key:"testkey",value:"testvalue"})'
         == 'E("_set_local_storage", {key:"testkey",value:"testvalue"})'
@@ -249,5 +262,6 @@ def test_remove_local_storage():
     spec = event.remove_local_storage("testkey")
     spec = event.remove_local_storage("testkey")
     assert isinstance(spec, EventSpec)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_remove_local_storage"
     assert spec.handler.fn.__qualname__ == "_remove_local_storage"
-    assert spec.args == (("key", "testkey"),)
+    assert spec.args[0][0].equals(Var.create_safe("key"))
+    assert spec.args[0][1].equals(Var.create_safe("testkey"))
     assert format.format_event(spec) == 'E("_remove_local_storage", {key:"testkey"})'
     assert format.format_event(spec) == 'E("_remove_local_storage", {key:"testkey"})'

+ 36 - 25
tests/test_state.py

@@ -408,18 +408,18 @@ def test_get_class_substate():
 
 
 def test_get_class_var():
 def test_get_class_var():
     """Test getting the var of a class."""
     """Test getting the var of a class."""
-    assert TestState.get_class_var(("num1",)) == TestState.num1
-    assert TestState.get_class_var(("num2",)) == TestState.num2
-    assert ChildState.get_class_var(("value",)) == ChildState.value
-    assert GrandchildState.get_class_var(("value2",)) == GrandchildState.value2
-    assert TestState.get_class_var(("child_state", "value")) == ChildState.value
-    assert (
-        TestState.get_class_var(("child_state", "grandchild_state", "value2"))
-        == GrandchildState.value2
+    assert TestState.get_class_var(("num1",)).equals(TestState.num1)
+    assert TestState.get_class_var(("num2",)).equals(TestState.num2)
+    assert ChildState.get_class_var(("value",)).equals(ChildState.value)
+    assert GrandchildState.get_class_var(("value2",)).equals(GrandchildState.value2)
+    assert TestState.get_class_var(("child_state", "value")).equals(ChildState.value)
+    assert TestState.get_class_var(
+        ("child_state", "grandchild_state", "value2")
+    ).equals(
+        GrandchildState.value2,
     )
     )
-    assert (
-        ChildState.get_class_var(("grandchild_state", "value2"))
-        == GrandchildState.value2
+    assert ChildState.get_class_var(("grandchild_state", "value2")).equals(
+        GrandchildState.value2,
     )
     )
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
         TestState.get_class_var(("invalid_var",))
         TestState.get_class_var(("invalid_var",))
@@ -837,21 +837,32 @@ def test_get_query_params(test_state):
     assert test_state.get_query_params() == params
     assert test_state.get_query_params() == params
 
 
 
 
-def test_add_var(test_state):
-    test_state.add_var("dynamic_int", int, 42)
-    assert test_state.dynamic_int == 42
-
-    test_state.add_var("dynamic_list", List[int], [5, 10])
-    assert test_state.dynamic_list == [5, 10]
-    assert test_state.dynamic_list == [5, 10]
-
-    # how to test that one?
-    # test_state.dynamic_list.append(15)
-    # assert test_state.dynamic_list == [5, 10, 15]
+def test_add_var():
+    class DynamicState(State):
+        pass
 
 
-    test_state.add_var("dynamic_dict", Dict[str, int], {"k1": 5, "k2": 10})
-    assert test_state.dynamic_dict == {"k1": 5, "k2": 10}
-    assert test_state.dynamic_dict == {"k1": 5, "k2": 10}
+    ds1 = DynamicState()
+    assert "dynamic_int" not in ds1.__dict__
+    assert not hasattr(ds1, "dynamic_int")
+    ds1.add_var("dynamic_int", int, 42)
+    # Existing instances get the BaseVar
+    assert ds1.dynamic_int.equals(DynamicState.dynamic_int)  # type: ignore
+    # New instances get an actual value with the default
+    assert DynamicState().dynamic_int == 42
+
+    ds1.add_var("dynamic_list", List[int], [5, 10])
+    assert ds1.dynamic_list.equals(DynamicState.dynamic_list)  # type: ignore
+    ds2 = DynamicState()
+    assert ds2.dynamic_list == [5, 10]
+    ds2.dynamic_list.append(15)
+    assert ds2.dynamic_list == [5, 10, 15]
+    assert DynamicState().dynamic_list == [5, 10]
+
+    ds1.add_var("dynamic_dict", Dict[str, int], {"k1": 5, "k2": 10})
+    assert ds1.dynamic_dict.equals(DynamicState.dynamic_dict)  # type: ignore
+    assert ds2.dynamic_dict.equals(DynamicState.dynamic_dict)  # type: ignore
+    assert DynamicState().dynamic_dict == {"k1": 5, "k2": 10}
+    assert DynamicState().dynamic_dict == {"k1": 5, "k2": 10}
 
 
 
 
 def test_add_var_default_handlers(test_state):
 def test_add_var_default_handlers(test_state):