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

This commit is contained in:
Elijah Ahianyo 2024-10-03 17:35:34 +00:00 committed by GitHub
parent 12d73e4167
commit 4b3d056212
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 0 deletions

View File

@ -74,6 +74,7 @@ from reflex.utils.exceptions import (
EventHandlerShadowsBuiltInStateMethod, EventHandlerShadowsBuiltInStateMethod,
ImmutableStateError, ImmutableStateError,
LockExpiredError, LockExpiredError,
SetUndefinedStateVarError,
) )
from reflex.utils.exec import is_testing_env from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer from reflex.utils.serializers import serializer
@ -1260,6 +1261,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Args: Args:
name: The name of the attribute. name: The name of the attribute.
value: The value 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): if isinstance(value, MutableProxy):
# unwrap proxy objects when assigning back to the state # unwrap proxy objects when assigning back to the state
@ -1277,6 +1281,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
self._mark_dirty() self._mark_dirty()
return 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. # Set the attribute.
super().__setattr__(name, value) super().__setattr__(name, value)

View File

@ -115,3 +115,7 @@ class PrimitiveUnserializableToJSON(ReflexError, ValueError):
class InvalidLifespanTaskType(ReflexError, TypeError): class InvalidLifespanTaskType(ReflexError, TypeError):
"""Raised when an invalid task type is registered as a lifespan task.""" """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."""

View File

@ -41,6 +41,7 @@ from reflex.state import (
) )
from reflex.testing import chdir from reflex.testing import chdir
from reflex.utils import format, prerequisites, types from reflex.utils import format, prerequisites, types
from reflex.utils.exceptions import SetUndefinedStateVarError
from reflex.utils.format import json_dumps from reflex.utils.format import json_dumps
from reflex.vars.base import ComputedVar, Var from reflex.vars.base import ComputedVar, Var
from tests.units.states.mutation import MutableSQLAModel, MutableTestState 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" in ChildUsesMixinState.inherited_vars
assert "computed" not in ChildUsesMixinState.computed_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()