diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 9adc175d154..cbc0e192e5c 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -560,11 +560,15 @@ def __getattr__(self, __name: str) -> Any: ) if ( - not isinstance(self.__wrapped__, Base) - or __name not in NEVER_WRAP_BASE_ATTRS - ) and hasattr(value, "__func__"): + ( + not isinstance(self.__wrapped__, Base) + or __name not in NEVER_WRAP_BASE_ATTRS + ) + and (func := getattr(value, "__func__", None)) is not None + and not inspect.isclass(getattr(value, "__self__", None)) + ): # Rebind `self` to the proxy on methods to capture nested mutations. - return functools.partial(value.__func__, self) # pyright: ignore [reportFunctionMemberAccess, reportAttributeAccessIssue] + return functools.partial(func, self) if is_mutable_type(type(value)) and __name not in ( "__wrapped__", diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 941c569095f..4c55ea7f43b 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -2089,6 +2089,18 @@ def append_to_ls(self, item: dict): """ self.ls.append(item) + @classmethod + def from_dict(cls, data: dict) -> ModelDC: + """Create an instance of ModelDC from a dictionary. + + Args: + data: The dictionary to create the instance from. + + Returns: + An instance of ModelDC. + """ + return cls(**data) + @pytest.mark.asyncio async def test_state_proxy( @@ -3945,6 +3957,11 @@ def test_mutable_models(): assert state.dirty_vars == set() state.dc.append_to_ls({"new": "item"}) assert state.dirty_vars == {"dc"} + state.dirty_vars.clear() + + dc_from_dict = state.dc.from_dict({"foo": "from_dict", "ls": []}) + assert dc_from_dict == ModelDC(foo="from_dict", ls=[]) + assert state.dirty_vars == set() def test_dict_and_get_delta():