From 8e66c9b3c75282c8439ace67c2c4559311b51a27 Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Fri, 28 Jun 2024 01:03:36 +0200 Subject: [PATCH] classvars should not be backend vars (#3578) * classvars should not be backend vars * cleanup RESERVED_BACKEND_VAR_NAMES --- integration/test_computed_vars.py | 7 ++++++- reflex/state.py | 4 ---- reflex/utils/types.py | 18 ++++++++++++++++-- tests/utils/test_utils.py | 4 +++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/integration/test_computed_vars.py b/integration/test_computed_vars.py index 5b8da7571..39a6b9979 100644 --- a/integration/test_computed_vars.py +++ b/integration/test_computed_vars.py @@ -15,7 +15,10 @@ def ComputedVars(): """Test app for computed vars.""" import reflex as rx - class State(rx.State): + class StateMixin(rx.State, mixin=True): + pass + + class State(StateMixin, rx.State): count: int = 0 # cached var with dep on count @@ -57,6 +60,8 @@ def ComputedVars(): def mark_dirty(self): self._mark_dirty() + assert State.backend_vars == {} + def index() -> rx.Component: return rx.center( rx.vstack( diff --git a/reflex/state.py b/reflex/state.py index ce88b7f5a..73ebc0892 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -201,10 +201,6 @@ def _no_chain_background_task( RESERVED_BACKEND_VAR_NAMES = { "_backend_vars", - "_computed_var_dependencies", - "_substate_var_dependencies", - "_always_dirty_computed_vars", - "_always_dirty_substates", "_was_touched", } diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 6c759bf32..047c7e2b2 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -10,6 +10,7 @@ from functools import wraps from typing import ( Any, Callable, + ClassVar, Dict, Iterable, List, @@ -413,9 +414,22 @@ def is_backend_variable(name: str, cls: Type | None = None) -> bool: Returns: bool: The result of the check """ - if cls is not None and name.startswith(f"_{cls.__name__}__"): + if not name.startswith("_"): return False - return name.startswith("_") and not name.startswith("__") + + if name.startswith("__"): + return False + + if cls is not None: + if name.startswith(f"_{cls.__name__}__"): + return False + hints = get_type_hints(cls) + if name in hints: + hint = get_origin(hints[name]) + if hint == ClassVar: + return False + + return True def check_type_in_allowed_types(value_type: Type, allowed_types: Iterable) -> bool: diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 79dda0a70..e5c99511f 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -1,7 +1,7 @@ import os import typing from pathlib import Path -from typing import Any, List, Literal, Union +from typing import Any, ClassVar, List, Literal, Union import pytest import typer @@ -149,6 +149,7 @@ def test_backend_variable_cls(): class TestBackendVariable: """Test backend variable.""" + _classvar: ClassVar[int] = 0 _hidden: int = 0 not_hidden: int = 0 __dunderattr__: int = 0 @@ -159,6 +160,7 @@ def test_backend_variable_cls(): @pytest.mark.parametrize( "input, output", [ + ("_classvar", False), ("_hidden", True), ("not_hidden", False), ("__dundermethod__", False),