From 9ca5d4a095731e75ab868edafd61fe78304fc5ee Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 27 Sep 2024 12:04:43 -0700 Subject: [PATCH] Track backend-only vars that are declared without a default value (#4016) * Track backend-only vars that are declared without a default value Without this provision, declared backend vars can be accidentally shared among all states if a mutable value is assigned to the class attribute. * add test case for no default backend var --- reflex/state.py | 10 ++++++++++ tests/units/test_state.py | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/reflex/state.py b/reflex/state.py index c16b37b69..6af70db14 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -30,6 +30,7 @@ from typing import ( Type, Union, cast, + get_type_hints, ) import dill @@ -573,6 +574,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for name, value in cls.__dict__.items() if types.is_backend_base_variable(name, cls) } + # Add annotated backend vars that do not have a default value. + new_backend_vars.update( + { + name: Var("", _var_type=annotation_value).get_default_value() + for name, annotation_value in get_type_hints(cls).items() + if name not in new_backend_vars + and types.is_backend_base_variable(name, cls) + } + ) cls.backend_vars = { **cls.inherited_backend_vars, diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 89aad1536..205162b9f 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -3216,6 +3216,7 @@ class MixinState(State, mixin=True): num: int = 0 _backend: int = 0 + _backend_no_default: dict @rx.var(cache=True) def computed(self) -> str: @@ -3243,11 +3244,16 @@ def test_mixin_state() -> None: """Test that a mixin state works correctly.""" assert "num" in UsesMixinState.base_vars assert "num" in UsesMixinState.vars - assert UsesMixinState.backend_vars == {"_backend": 0} + assert UsesMixinState.backend_vars == {"_backend": 0, "_backend_no_default": {}} assert "computed" in UsesMixinState.computed_vars assert "computed" in UsesMixinState.vars + assert ( + UsesMixinState(_reflex_internal_init=True)._backend_no_default # type: ignore + is not UsesMixinState.backend_vars["_backend_no_default"] + ) + def test_child_mixin_state() -> None: """Test that mixin vars are only applied to the highest state in the hierarchy."""