Ver código fonte

Forbid Computed var shadowing (#3843)

benedikt-bartscher 8 meses atrás
pai
commit
d672c643b3
2 arquivos alterados com 49 adições e 4 exclusões
  1. 19 2
      reflex/state.py
  2. 30 2
      tests/test_var.py

+ 19 - 2
reflex/state.py

@@ -477,7 +477,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         cls._check_overridden_methods()
 
         # Computed vars should not shadow builtin state props.
-        cls._check_overriden_basevars()
+        cls._check_overridden_basevars()
 
         # Reset subclass tracking for this class.
         cls.class_subclasses = set()
@@ -505,6 +505,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
 
         # Get computed vars.
         computed_vars = cls._get_computed_vars()
+        cls._check_overridden_computed_vars()
 
         new_backend_vars = {
             name: value
@@ -718,7 +719,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             )
 
     @classmethod
-    def _check_overriden_basevars(cls):
+    def _check_overridden_basevars(cls):
         """Check for shadow base vars and raise error if any.
 
         Raises:
@@ -730,6 +731,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
                     f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
                 )
 
+    @classmethod
+    def _check_overridden_computed_vars(cls) -> None:
+        """Check for shadow computed vars and raise error if any.
+
+        Raises:
+            NameError: When a computed var shadows another.
+        """
+        for name, cv in cls.__dict__.items():
+            if not isinstance(cv, (ComputedVar, ImmutableComputedVar)):
+                continue
+            name = cv._var_name
+            if name in cls.inherited_vars or name in cls.inherited_backend_vars:
+                raise NameError(
+                    f"The computed var name `{cv._var_name}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
+                )
+
     @classmethod
     def get_skip_vars(cls) -> set[str]:
         """Get the vars to skip when serializing.

+ 30 - 2
tests/test_var.py

@@ -79,6 +79,11 @@ def ChildState(ParentState, TestObj):
     class ChildState(ParentState):
         @immutable_computed_var
         def var_without_annotation(self):
+            """This shadows ParentState.var_without_annotation.
+
+            Returns:
+                TestObj: Test object.
+            """
             return TestObj
 
     return ChildState
@@ -89,6 +94,11 @@ def GrandChildState(ChildState, TestObj):
     class GrandChildState(ChildState):
         @immutable_computed_var
         def var_without_annotation(self):
+            """This shadows ChildState.var_without_annotation.
+
+            Returns:
+                TestObj: Test object.
+            """
             return TestObj
 
     return GrandChildState
@@ -738,8 +748,6 @@ def test_var_unsupported_indexing_dicts(var, index):
     "fixture",
     [
         "ParentState",
-        "ChildState",
-        "GrandChildState",
         "StateWithAnyVar",
     ],
 )
@@ -761,6 +769,26 @@ def test_computed_var_without_annotation_error(request, fixture):
         )
 
 
+@pytest.mark.parametrize(
+    "fixture",
+    [
+        "ChildState",
+        "GrandChildState",
+    ],
+)
+def test_shadow_computed_var_error(request: pytest.FixtureRequest, fixture: str):
+    """Test that a name error is thrown when an attribute of a computed var is
+    shadowed by another attribute.
+
+    Args:
+        request: Fixture Request.
+        fixture: The state fixture.
+    """
+    with pytest.raises(NameError):
+        state = request.getfixturevalue(fixture)
+        state.var_without_annotation.foo
+
+
 @pytest.mark.parametrize(
     "fixture",
     [