Skip to content

Commit d672c64

Browse files
Forbid Computed var shadowing (#3843)
1 parent a5c73ad commit d672c64

2 files changed

Lines changed: 49 additions & 4 deletions

File tree

reflex/state.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
477477
cls._check_overridden_methods()
478478

479479
# Computed vars should not shadow builtin state props.
480-
cls._check_overriden_basevars()
480+
cls._check_overridden_basevars()
481481

482482
# Reset subclass tracking for this class.
483483
cls.class_subclasses = set()
@@ -505,6 +505,7 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
505505

506506
# Get computed vars.
507507
computed_vars = cls._get_computed_vars()
508+
cls._check_overridden_computed_vars()
508509

509510
new_backend_vars = {
510511
name: value
@@ -718,7 +719,7 @@ def _check_overridden_methods(cls):
718719
)
719720

720721
@classmethod
721-
def _check_overriden_basevars(cls):
722+
def _check_overridden_basevars(cls):
722723
"""Check for shadow base vars and raise error if any.
723724
724725
Raises:
@@ -730,6 +731,22 @@ def _check_overriden_basevars(cls):
730731
f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
731732
)
732733

734+
@classmethod
735+
def _check_overridden_computed_vars(cls) -> None:
736+
"""Check for shadow computed vars and raise error if any.
737+
738+
Raises:
739+
NameError: When a computed var shadows another.
740+
"""
741+
for name, cv in cls.__dict__.items():
742+
if not isinstance(cv, (ComputedVar, ImmutableComputedVar)):
743+
continue
744+
name = cv._var_name
745+
if name in cls.inherited_vars or name in cls.inherited_backend_vars:
746+
raise NameError(
747+
f"The computed var name `{cv._var_name}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
748+
)
749+
733750
@classmethod
734751
def get_skip_vars(cls) -> set[str]:
735752
"""Get the vars to skip when serializing.

tests/test_var.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ def ChildState(ParentState, TestObj):
7979
class ChildState(ParentState):
8080
@immutable_computed_var
8181
def var_without_annotation(self):
82+
"""This shadows ParentState.var_without_annotation.
83+
84+
Returns:
85+
TestObj: Test object.
86+
"""
8287
return TestObj
8388

8489
return ChildState
@@ -89,6 +94,11 @@ def GrandChildState(ChildState, TestObj):
8994
class GrandChildState(ChildState):
9095
@immutable_computed_var
9196
def var_without_annotation(self):
97+
"""This shadows ChildState.var_without_annotation.
98+
99+
Returns:
100+
TestObj: Test object.
101+
"""
92102
return TestObj
93103

94104
return GrandChildState
@@ -738,8 +748,6 @@ def test_var_unsupported_indexing_dicts(var, index):
738748
"fixture",
739749
[
740750
"ParentState",
741-
"ChildState",
742-
"GrandChildState",
743751
"StateWithAnyVar",
744752
],
745753
)
@@ -761,6 +769,26 @@ def test_computed_var_without_annotation_error(request, fixture):
761769
)
762770

763771

772+
@pytest.mark.parametrize(
773+
"fixture",
774+
[
775+
"ChildState",
776+
"GrandChildState",
777+
],
778+
)
779+
def test_shadow_computed_var_error(request: pytest.FixtureRequest, fixture: str):
780+
"""Test that a name error is thrown when an attribute of a computed var is
781+
shadowed by another attribute.
782+
783+
Args:
784+
request: Fixture Request.
785+
fixture: The state fixture.
786+
"""
787+
with pytest.raises(NameError):
788+
state = request.getfixturevalue(fixture)
789+
state.var_without_annotation.foo
790+
791+
764792
@pytest.mark.parametrize(
765793
"fixture",
766794
[

0 commit comments

Comments
 (0)