writer-framework icon indicating copy to clipboard operation
writer-framework copied to clipboard

Mutations for StateProxy-contained lists may not be tracked

Open mmikita95 opened this issue 1 year ago • 4 comments

After working on #204, I've noticed that there are some unexpected behaviors happening in relation to lists that are stored in StateProxy. Consider the following example:

from streamsync.core import StateProxy

sp = StateProxy(
            {
                'name': 'Robert',
                'age': 1,
                'interests': ["lamps", "cars"]
            }
        )
            
mutations = sp.get_mutations_as_dict()
assert(len(mutations) == 3)  # True, because 3 initial keys were added to a previously-empty state

# The following modification of the state invokes __setitem__, and will be tracked as a mutation
sp['interests'] = ["lamps", "cars", "dogs"]
mutations = sp.get_mutations_as_dict()
assert(len(mutations) == 1)  
# True, because sp['interests'] were modified

But:

# The following modification of the state does not invoke __setitem__
sp['interests'].append("pigeons")
mutations = sp.get_mutations_as_dict()

assert(len(mutations) == 1)  
# False, because despite sp['interests'] was modified, 
# the mutation wasn't tracked due to the __setitem__ not being invoked

In certain configurations, this leads to lists not being properly updated on the frontend.

We'll probably have to introduce a solution similar to how dictionaries are currently processed inside the StateProxy, for example by providing a wrapper class that will override all the list modification methods and message the updates of its containing list to the related StateProxy. I'll greatly appreciate any feedback and contributions while I'll be looking into it.

mmikita95 avatar Jan 24 '24 12:01 mmikita95

Hi Mikita, as discussed earlier let's treat this as a potential enhancement. As discussed here, mutation detection happens by assignment and List is no exception.

ramedina86 avatar Jan 24 '24 13:01 ramedina86

@raaymax wants to fix this :)

ramedina86 avatar Feb 29 '24 14:02 ramedina86

@mmikita95 I think this one has been delivered in 0.4.0. Could you confirm ?

FabienArcellier avatar Apr 08 '24 06:04 FabienArcellier

@FabienArcellier sadly, can't confirm. Good news is that the state keeps this change – I think I remember that it was not the case, but the change still doesn't get propagated to the frontend instantly – it has to "wait" on another, "trackable" change.

This is the setup I used to test if you're interested: main.py

import streamsync as ss

def implicit_list_modification(state):
    state["list_to_be_modified"].append("test")

def explicit_list_modification(state):
    state["list_to_be_modified"] += ["test"]

initial_state = ss.init_state({"list_to_be_modified": []})

ui.json

{
    "metadata": {
        "streamsync_version": "0.4.0"
    },
    "components": {
        "root": {
            "id": "root",
            "type": "root",
            "content": {
                "appName": "List modification test"
            },
            "isCodeManaged": false,
            "position": 0
        },
        "main-page": {
            "id": "main-page",
            "type": "page",
            "content": {
                "pageMode": "",
                "key": "main"
            },
            "isCodeManaged": false,
            "position": 0,
            "parentId": "root"
        },
        "d2w3obvhbri2sbut": {
            "id": "d2w3obvhbri2sbut",
            "type": "columns",
            "content": {},
            "isCodeManaged": false,
            "position": 0,
            "parentId": "f2qst53hgdfydwyw",
            "handlers": {},
            "visible": true
        },
        "437w7haqfmf98fx7": {
            "id": "437w7haqfmf98fx7",
            "type": "column",
            "content": {
                "width": "1"
            },
            "isCodeManaged": false,
            "position": 0,
            "parentId": "d2w3obvhbri2sbut",
            "handlers": {},
            "visible": true
        },
        "3qqlvgpygldtm3zu": {
            "id": "3qqlvgpygldtm3zu",
            "type": "column",
            "content": {
                "width": "1"
            },
            "isCodeManaged": false,
            "position": 1,
            "parentId": "d2w3obvhbri2sbut",
            "handlers": {},
            "visible": true
        },
        "f2kq09lfygpmx8ot": {
            "id": "f2kq09lfygpmx8ot",
            "type": "button",
            "content": {
                "text": "Test implicit list modification"
            },
            "isCodeManaged": false,
            "position": 0,
            "parentId": "437w7haqfmf98fx7",
            "handlers": {
                "ss-click": "implicit_list_modification"
            },
            "visible": true
        },
        "pz9w7yngw9o4iqxz": {
            "id": "pz9w7yngw9o4iqxz",
            "type": "text",
            "content": {
                "text": "@{list_to_be_modified}"
            },
            "isCodeManaged": false,
            "position": 0,
            "parentId": "3qqlvgpygldtm3zu",
            "handlers": {},
            "visible": true
        },
        "uzp2y2hlnsfyck32": {
            "id": "uzp2y2hlnsfyck32",
            "type": "button",
            "content": {
                "text": "Test explicit list modification"
            },
            "isCodeManaged": false,
            "position": 1,
            "parentId": "437w7haqfmf98fx7",
            "handlers": {
                "ss-click": "explicit_list_modification"
            },
            "visible": true
        },
        "f2qst53hgdfydwyw": {
            "id": "f2qst53hgdfydwyw",
            "type": "section",
            "content": {
                "title": ""
            },
            "isCodeManaged": false,
            "position": 0,
            "parentId": "main-page",
            "handlers": {},
            "visible": true
        }
    }
}

Expected effect is that "test" appears in the list on button press for both cases – but, in fact, the "test"s appended by the click(s) of the first button only appear when the second, "explicit" button is pressed.

mmikita95 avatar Apr 08 '24 07:04 mmikita95