Skip to content

Commit 99ffbc8

Browse files
fix var dependency dicts (#3842)
1 parent fd13e55 commit 99ffbc8

3 files changed

Lines changed: 64 additions & 22 deletions

File tree

reflex/state.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,9 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
584584
cls.event_handlers[name] = handler
585585
setattr(cls, name, handler)
586586

587+
# Initialize per-class var dependency tracking.
588+
cls._computed_var_dependencies = defaultdict(set)
589+
cls._substate_var_dependencies = defaultdict(set)
587590
cls._init_var_dependency_dicts()
588591

589592
@staticmethod
@@ -653,10 +656,6 @@ def _init_var_dependency_dicts(cls):
653656
Additional updates tracking dicts for vars and substates that always
654657
need to be recomputed.
655658
"""
656-
# Initialize per-class var dependency tracking.
657-
cls._computed_var_dependencies = defaultdict(set)
658-
cls._substate_var_dependencies = defaultdict(set)
659-
660659
inherited_vars = set(cls.inherited_vars).union(
661660
set(cls.inherited_backend_vars),
662661
)
@@ -1006,20 +1005,20 @@ def setup_dynamic_args(cls, args: dict[str, str]):
10061005
Args:
10071006
args: a dict of args
10081007
"""
1008+
if not args:
1009+
return
10091010

10101011
def argsingle_factory(param):
1011-
@ComputedVar
10121012
def inner_func(self) -> str:
10131013
return self.router.page.params.get(param, "")
10141014

1015-
return inner_func
1015+
return ComputedVar(fget=inner_func, cache=True)
10161016

10171017
def arglist_factory(param):
1018-
@ComputedVar
10191018
def inner_func(self) -> List:
10201019
return self.router.page.params.get(param, [])
10211020

1022-
return inner_func
1021+
return ComputedVar(fget=inner_func, cache=True)
10231022

10241023
for param, value in args.items():
10251024
if value == constants.RouteArgType.SINGLE:
@@ -1033,8 +1032,8 @@ def inner_func(self) -> List:
10331032
cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls) # type: ignore
10341033
setattr(cls, param, func)
10351034

1036-
# Reinitialize dependency tracking dicts.
1037-
cls._init_var_dependency_dicts()
1035+
# Reinitialize dependency tracking dicts.
1036+
cls._init_var_dependency_dicts()
10381037

10391038
def __getattribute__(self, name: str) -> Any:
10401039
"""Get the state var.
@@ -3600,5 +3599,7 @@ def reload_state_module(
36003599
if subclass.__module__ == module and module is not None:
36013600
state.class_subclasses.remove(subclass)
36023601
state._always_dirty_substates.discard(subclass.get_name())
3603-
state._init_var_dependency_dicts()
3602+
state._computed_var_dependencies = defaultdict(set)
3603+
state._substate_var_dependencies = defaultdict(set)
3604+
state._init_var_dependency_dicts()
36043605
state.get_class_substate.cache_clear()

tests/test_app.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ def on_counter(self):
906906
"""Increment the counter var."""
907907
self.counter = self.counter + 1
908908

909-
@computed_var
909+
@computed_var(cache=True)
910910
def comp_dynamic(self) -> str:
911911
"""A computed var that depends on the dynamic var.
912912
@@ -1049,9 +1049,6 @@ def _dynamic_state_event(name, val, **kwargs):
10491049
assert on_load_update == StateUpdate(
10501050
delta={
10511051
state.get_name(): {
1052-
# These computed vars _shouldn't_ be here, because they didn't change
1053-
arg_name: exp_val,
1054-
f"comp_{arg_name}": exp_val,
10551052
"loaded": exp_index + 1,
10561053
},
10571054
},
@@ -1073,9 +1070,6 @@ def _dynamic_state_event(name, val, **kwargs):
10731070
assert on_set_is_hydrated_update == StateUpdate(
10741071
delta={
10751072
state.get_name(): {
1076-
# These computed vars _shouldn't_ be here, because they didn't change
1077-
arg_name: exp_val,
1078-
f"comp_{arg_name}": exp_val,
10791073
"is_hydrated": True,
10801074
},
10811075
},
@@ -1097,9 +1091,6 @@ def _dynamic_state_event(name, val, **kwargs):
10971091
assert update == StateUpdate(
10981092
delta={
10991093
state.get_name(): {
1100-
# These computed vars _shouldn't_ be here, because they didn't change
1101-
f"comp_{arg_name}": exp_val,
1102-
arg_name: exp_val,
11031094
"counter": exp_index + 1,
11041095
}
11051096
},

tests/test_state.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,12 @@ def test_set_dirty_var(test_state):
649649
assert test_state.dirty_vars == set()
650650

651651

652-
def test_set_dirty_substate(test_state, child_state, child_state2, grandchild_state):
652+
def test_set_dirty_substate(
653+
test_state: TestState,
654+
child_state: ChildState,
655+
child_state2: ChildState2,
656+
grandchild_state: GrandchildState,
657+
):
653658
"""Test changing substate vars marks the value as dirty.
654659
655660
Args:
@@ -3077,6 +3082,51 @@ def bar(self) -> str:
30773082
assert C1._potentially_dirty_substates() == set()
30783083

30793084

3085+
def test_router_var_dep() -> None:
3086+
"""Test that router var dependencies are correctly tracked."""
3087+
3088+
class RouterVarParentState(State):
3089+
"""A parent state for testing router var dependency."""
3090+
3091+
pass
3092+
3093+
class RouterVarDepState(RouterVarParentState):
3094+
"""A state with a router var dependency."""
3095+
3096+
@rx.var(cache=True)
3097+
def foo(self) -> str:
3098+
return self.router.page.params.get("foo", "")
3099+
3100+
foo = RouterVarDepState.computed_vars["foo"]
3101+
State._init_var_dependency_dicts()
3102+
3103+
assert foo._deps(objclass=RouterVarDepState) == {"router"}
3104+
assert RouterVarParentState._potentially_dirty_substates() == {RouterVarDepState}
3105+
assert RouterVarParentState._substate_var_dependencies == {
3106+
"router": {RouterVarDepState.get_name()}
3107+
}
3108+
assert RouterVarDepState._computed_var_dependencies == {
3109+
"router": {"foo"},
3110+
}
3111+
3112+
rx_state = State()
3113+
parent_state = RouterVarParentState()
3114+
state = RouterVarDepState()
3115+
3116+
# link states
3117+
rx_state.substates = {RouterVarParentState.get_name(): parent_state}
3118+
parent_state.parent_state = rx_state
3119+
state.parent_state = parent_state
3120+
parent_state.substates = {RouterVarDepState.get_name(): state}
3121+
3122+
assert state.dirty_vars == set()
3123+
3124+
# Reassign router var
3125+
state.router = state.router
3126+
assert state.dirty_vars == {"foo", "router"}
3127+
assert parent_state.dirty_substates == {RouterVarDepState.get_name()}
3128+
3129+
30803130
@pytest.mark.asyncio
30813131
async def test_setvar(mock_app: rx.App, token: str):
30823132
"""Test that setvar works correctly.

0 commit comments

Comments
 (0)