[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,
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)

View File

@ -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."""

View File

@ -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()