Skip to content

Commit

Permalink
[ENG-3476] Setting State Vars that are not defined should raise an er…
Browse files Browse the repository at this point in the history
…ror (#4007)
  • Loading branch information
ElijahAhianyo authored Oct 3, 2024
1 parent 12d73e4 commit 4b3d056
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 0 deletions.
15 changes: 15 additions & 0 deletions reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
EventHandlerShadowsBuiltInStateMethod,
ImmutableStateError,
LockExpiredError,
SetUndefinedStateVarError,
)
from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer
Expand Down Expand Up @@ -1260,6 +1261,9 @@ def __setattr__(self, name: str, value: Any):
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
Expand All @@ -1277,6 +1281,17 @@ def __setattr__(self, name: str, value: Any):
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)

Expand Down
4 changes: 4 additions & 0 deletions reflex/utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 43 additions & 0 deletions tests/units/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
)
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
Expand Down Expand Up @@ -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()

0 comments on commit 4b3d056

Please sign in to comment.