Skip to content

Commit 982d2fa

Browse files
authored
ENG-8227: always _clean() after app.modify_state (#5949)
app.modify_state might not always generate a delta, but it _does_ always need to be cleaned so that dirty backend vars and dirty substates (with backend vars) are not persisted to redis (and subsequently reloaded when the referenced states may not be cached)
1 parent 6663dfd commit 982d2fa

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

reflex/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,9 +1580,9 @@ async def modify_state(
15801580
# No other event handler can modify the state while in this context.
15811581
yield state
15821582
delta = await state._get_resolved_delta()
1583+
state._clean()
15831584
if delta:
1584-
# When the state is modified reset dirty status and emit the delta to the frontend.
1585-
state._clean()
1585+
# When the frontend vars are modified emit the delta to the frontend.
15861586
await self.event_namespace.emit_update(
15871587
update=StateUpdate(
15881588
delta=delta,

tests/units/test_app.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,3 +1942,71 @@ def test_backend_exception_handler_validation(handler_fn, expected):
19421942
"""
19431943
with expected:
19441944
rx.App(backend_exception_handler=handler_fn)._validate_exception_handlers()
1945+
1946+
1947+
@pytest.mark.parametrize(
1948+
("substate", "frontend"),
1949+
[
1950+
pytest.param(False, True, id="root_state_frontend"),
1951+
pytest.param(False, False, id="root_state_backend"),
1952+
pytest.param(True, True, id="substate_frontend"),
1953+
pytest.param(True, False, id="substate_backend"),
1954+
],
1955+
)
1956+
@pytest.mark.asyncio
1957+
async def test_app_modify_state_clean(token: str, substate: bool, frontend: bool):
1958+
"""Test that modify_state does not leave dirty_vars or dirty_substates.
1959+
1960+
Args:
1961+
token: A client token.
1962+
substate: Whether to modify a substate.
1963+
frontend: Whether to modify a frontend or backend var.
1964+
"""
1965+
1966+
class Base(BaseState):
1967+
count: int = 0
1968+
_backend: int = 0
1969+
1970+
class Sub(Base):
1971+
sub_count: int = 0
1972+
_sub_backend: int = 0
1973+
1974+
app = App(_state=Base)
1975+
app._event_namespace = AsyncMock()
1976+
1977+
async with app.modify_state(
1978+
token=_substate_key(token, Sub.get_name())
1979+
) as root_state:
1980+
sub = root_state.substates[Sub.get_name()]
1981+
if substate:
1982+
if frontend:
1983+
sub.sub_count = 1
1984+
else:
1985+
sub._sub_backend = 1
1986+
else:
1987+
if frontend:
1988+
root_state.count = 1
1989+
else:
1990+
root_state._backend = 1
1991+
1992+
assert not root_state.dirty_vars
1993+
assert not root_state.dirty_substates
1994+
if substate:
1995+
assert sub._was_touched
1996+
assert not root_state._was_touched
1997+
else:
1998+
assert root_state._was_touched
1999+
assert not sub._was_touched
2000+
2001+
if frontend:
2002+
assert app._event_namespace.emit_update.call_count == 1
2003+
if substate:
2004+
exp_delta = {Sub.get_full_name(): {"sub_count_rx_state_": 1}}
2005+
else:
2006+
exp_delta = {Base.get_full_name(): {"count_rx_state_": 1}}
2007+
assert (
2008+
app._event_namespace.emit_update.call_args.kwargs["update"].delta
2009+
== exp_delta
2010+
)
2011+
else:
2012+
assert app._event_namespace.emit_update.call_count == 0

0 commit comments

Comments
 (0)