Răsfoiți Sursa

[ENG-3476] Setting State Vars that are not defined should raise an error (#4007)

Elijah Ahianyo 7 luni în urmă
părinte
comite
4b3d056212
3 a modificat fișierele cu 62 adăugiri și 0 ștergeri
  1. 15 0
      reflex/state.py
  2. 4 0
      reflex/utils/exceptions.py
  3. 43 0
      tests/units/test_state.py

+ 15 - 0
reflex/state.py

@@ -74,6 +74,7 @@ from reflex.utils.exceptions import (
     EventHandlerShadowsBuiltInStateMethod,
     ImmutableStateError,
     LockExpiredError,
+    SetUndefinedStateVarError,
 )
 from reflex.utils.exec import is_testing_env
 from reflex.utils.serializers import serializer
@@ -1260,6 +1261,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         Args:
             name: The name of the attribute.
             value: The value of the attribute.
+
+        Raises:
+            SetUndefinedStateVarError: If a value of a var is set without first defining it.
         """
         if isinstance(value, MutableProxy):
             # unwrap proxy objects when assigning back to the state
@@ -1277,6 +1281,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             self._mark_dirty()
             return
 
+        if (
+            name not in self.vars
+            and name not in self.get_skip_vars()
+            and not name.startswith("__")
+            and not name.startswith(f"_{type(self).__name__}__")
+        ):
+            raise SetUndefinedStateVarError(
+                f"The state variable '{name}' has not been defined in '{type(self).__name__}'. "
+                f"All state variables must be declared before they can be set."
+            )
+
         # Set the attribute.
         super().__setattr__(name, value)
 

+ 4 - 0
reflex/utils/exceptions.py

@@ -115,3 +115,7 @@ class PrimitiveUnserializableToJSON(ReflexError, ValueError):
 
 class InvalidLifespanTaskType(ReflexError, TypeError):
     """Raised when an invalid task type is registered as a lifespan task."""
+
+
+class SetUndefinedStateVarError(ReflexError, AttributeError):
+    """Raised when setting the value of a var without first declaring it."""

+ 43 - 0
tests/units/test_state.py

@@ -41,6 +41,7 @@ from reflex.state import (
 )
 from reflex.testing import chdir
 from reflex.utils import format, prerequisites, types
+from reflex.utils.exceptions import SetUndefinedStateVarError
 from reflex.utils.format import json_dumps
 from reflex.vars.base import ComputedVar, Var
 from tests.units.states.mutation import MutableSQLAModel, MutableTestState
@@ -3262,3 +3263,45 @@ def test_child_mixin_state() -> None:
 
     assert "computed" in ChildUsesMixinState.inherited_vars
     assert "computed" not in ChildUsesMixinState.computed_vars
+
+
+def test_assignment_to_undeclared_vars():
+    """Test that an attribute error is thrown when undeclared vars are set."""
+
+    class State(BaseState):
+        val: str
+        _val: str
+        __val: str  # type: ignore
+
+        def handle_supported_regular_vars(self):
+            self.val = "no underscore"
+            self._val = "single leading underscore"
+            self.__val = "double leading undercore"
+
+        def handle_regular_var(self):
+            self.num = 5
+
+        def handle_backend_var(self):
+            self._num = 5
+
+        def handle_non_var(self):
+            self.__num = 5
+
+    class Substate(State):
+        def handle_var(self):
+            self.value = 20
+
+    state = State()  # type: ignore
+    sub_state = Substate()  # type: ignore
+
+    with pytest.raises(SetUndefinedStateVarError):
+        state.handle_regular_var()
+
+    with pytest.raises(SetUndefinedStateVarError):
+        sub_state.handle_var()
+
+    with pytest.raises(SetUndefinedStateVarError):
+        state.handle_backend_var()
+
+    state.handle_supported_regular_vars()
+    state.handle_non_var()