From b4de4393fa593635f87d7a93d9d87c76823ac7eb Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 21 Feb 2024 11:24:16 -0800 Subject: [PATCH 1/3] test_state: augment modify_state test for writing MutableProxy If the object contains a MutableProxy inside of it, then we get a pickling error. --- tests/test_state.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_state.py b/tests/test_state.py index dbcc1afce0d..565f1945b27 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1457,12 +1457,16 @@ async def test_state_manager_modify_state( token: A token. substate_token: A token + substate name for looking up in state manager. """ - async with state_manager.modify_state(substate_token): + async with state_manager.modify_state(substate_token) as state: if isinstance(state_manager, StateManagerRedis): assert await state_manager.redis.get(f"{token}_lock") elif isinstance(state_manager, StateManagerMemory): assert token in state_manager._states_locks assert state_manager._states_locks[token].locked() + # Should be able to write proxy objects inside mutables + complex_1 = state.complex[1] + assert isinstance(complex_1, MutableProxy) + state.complex[3] = complex_1 # lock should be dropped after exiting the context if isinstance(state_manager, StateManagerRedis): assert (await state_manager.redis.get(f"{token}_lock")) is None From 6a6f57a9d898db2aad570667d23a34803e0ebcc8 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 21 Feb 2024 11:25:36 -0800 Subject: [PATCH 2/3] Implement __reduce_ex__ for MutableProxy Pass through `__reduce_ex__` onto the wrapped instance to strip it off when cloudpickling to redis. --- reflex/state.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/reflex/state.py b/reflex/state.py index 7b377b4d083..f6bf2161b85 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2363,6 +2363,21 @@ def __deepcopy__(self, memo=None) -> Any: """ return copy.deepcopy(self.__wrapped__, memo=memo) + def __reduce_ex__(self, protocol_version): + """Get the state for redis serialization. + + This method is called by cloudpickle to serialize the object. + + It explicitly serializes the wrapped object, stripping off the mutable proxy. + + Args: + protocol_version: The protocol version. + + Returns: + Tuple of (wrapped class, empty args, class __getstate__) + """ + return self.__wrapped__.__reduce_ex__(protocol_version) + @serializer def serialize_mutable_proxy(mp: MutableProxy) -> SerializedType: From 1c6cb7a183f436886045609e39688d96a77b4ea5 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 21 Feb 2024 12:00:25 -0800 Subject: [PATCH 3/3] base: get_value actually works with a str key Unless the key isn't a field on the model, then it falls back to the previous behavior of just returning the given key as is... why does it do this? I don't know. --- reflex/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/reflex/base.py b/reflex/base.py index 9efea60e0ef..36b150c5ca8 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -115,6 +115,10 @@ def get_value(self, key: str) -> Any: Returns: The value of the field. """ + if isinstance(key, str) and key in self.__fields__: + # Seems like this function signature was wrong all along? + # If the user wants a field that we know of, get it and pass it off to _get_value + key = getattr(self, key) return self._get_value( key, to_dict=True,