Skip to content

Commit 5a47690

Browse files
committed
Don't wrap return values of MutableProxy methods
For arbitrary methods, we assume the return value is not associated directly with the dirty state of the class. If the method _does_ mutate some value on the `self`, then it will still be marked dirty by virtue of `self` being bound to the MutableProxy.
1 parent 4ee713f commit 5a47690

File tree

2 files changed

+27
-5
lines changed

2 files changed

+27
-5
lines changed

reflex/istate/proxy.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,8 @@ def __getattr__(self, __name: str) -> Any:
563563
not isinstance(self.__wrapped__, Base)
564564
or __name not in NEVER_WRAP_BASE_ATTRS
565565
) and hasattr(value, "__func__"):
566-
# Wrap methods which might do _anything_
567-
return wrapt.FunctionWrapper(
568-
functools.partial(value.__func__, self), # pyright: ignore [reportFunctionMemberAccess, reportAttributeAccessIssue]
569-
self._wrap_recursive_decorator, # pyright: ignore[reportArgumentType]
570-
)
566+
# Rebind `self` to the proxy on methods to capture nested mutations.
567+
return functools.partial(value.__func__, self) # pyright: ignore [reportFunctionMemberAccess, reportAttributeAccessIssue]
571568

572569
if is_mutable_type(type(value)) and __name not in (
573570
"__wrapped__",

tests/units/test_state.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,22 @@ def double_foo(self) -> str:
20712071
"""
20722072
return self.foo + self.foo
20732073

2074+
def copy(self, **kwargs) -> ModelDC:
2075+
"""Create a copy of the dataclass with updated fields.
2076+
2077+
Returns:
2078+
A new instance of ModelDC with updated fields.
2079+
"""
2080+
return dataclasses.replace(self, **kwargs)
2081+
2082+
def append_to_ls(self, item: dict):
2083+
"""Append an item to the list attribute ls.
2084+
2085+
Args:
2086+
item: The item to append.
2087+
"""
2088+
self.ls.append(item)
2089+
20742090

20752091
@pytest.mark.asyncio
20762092
async def test_state_proxy(
@@ -3918,6 +3934,15 @@ def test_mutable_models():
39183934
foo="larp", ls=[{"hi": "reflex"}]
39193935
)
39203936
assert state.dirty_vars == set()
3937+
dc_copy = state.dc.copy()
3938+
assert dc_copy == state.dc
3939+
assert dc_copy is not state.dc
3940+
dc_copy.foo = "new_foo"
3941+
assert state.dirty_vars == set()
3942+
dc_copy.append_to_ls({"new": "item"})
3943+
assert state.dirty_vars == set()
3944+
state.dc.append_to_ls({"new": "item"})
3945+
assert state.dirty_vars == {"dc"}
39213946

39223947

39233948
def test_dict_and_get_delta():

0 commit comments

Comments
 (0)