From 1aca1b677ff24b5b90cff228f91453859fa8b5f4 Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:37:05 +0100 Subject: [PATCH] fix is_backend_variable for dunder prefixed variables (#2391) --- reflex/state.py | 9 ++++++--- reflex/utils/types.py | 5 ++++- tests/utils/test_utils.py | 16 ++++++++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 8906de289..cc7b153d9 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -315,7 +315,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): cls.new_backend_vars = { name: value for name, value in cls.__dict__.items() - if types.is_backend_variable(name) + if types.is_backend_variable(name, cls) and name not in cls.inherited_backend_vars and not isinstance(value, FunctionType) } @@ -878,7 +878,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): setattr(self.parent_state, name, value) return - if types.is_backend_variable(name) and name not in RESERVED_BACKEND_VAR_NAMES: + if ( + types.is_backend_variable(name, self.__class__) + and name not in RESERVED_BACKEND_VAR_NAMES + ): self._backend_vars.__setitem__(name, value) self.dirty_vars.add(name) self._mark_dirty() @@ -1177,7 +1180,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): subdelta = { prop: getattr(self, prop) for prop in delta_vars - if not types.is_backend_variable(prop) + if not types.is_backend_variable(prop, self.__class__) } if len(subdelta) > 0: delta[self.get_full_name()] = subdelta diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 6191f5de3..06237d36a 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -251,15 +251,18 @@ def is_valid_var_type(type_: Type) -> bool: return _issubclass(type_, StateVar) or serializers.has_serializer(type_) -def is_backend_variable(name: str) -> bool: +def is_backend_variable(name: str, cls: Type | None = None) -> bool: """Check if this variable name correspond to a backend variable. Args: name: The name of the variable to check + cls: The class of the variable to check Returns: bool: The result of the check """ + if cls is not None and name.startswith(f"_{cls.__name__}__"): + return False return name.startswith("_") and not name.startswith("__") diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 2700cf364..fac3fc3b7 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -144,6 +144,18 @@ def test_setup_frontend(tmp_path, mocker): assert (web_public_folder / "favicon.ico").exists() +@pytest.fixture +def test_backend_variable_cls(): + class TestBackendVariable: + """Test backend variable.""" + + _hidden: int = 0 + not_hidden: int = 0 + __dunderattr__: int = 0 + + return TestBackendVariable + + @pytest.mark.parametrize( "input, output", [ @@ -152,8 +164,8 @@ def test_setup_frontend(tmp_path, mocker): ("__dundermethod__", False), ], ) -def test_is_backend_variable(input, output): - assert types.is_backend_variable(input) == output +def test_is_backend_variable(test_backend_variable_cls, input, output): + assert types.is_backend_variable(input, test_backend_variable_cls) == output @pytest.mark.parametrize(