Skip to content

Commit a987437

Browse files
authored
Don't wrap return values of MutableProxy methods (#5986)
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 a987437

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)