Mutations for StateProxy-contained lists may not be tracked
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.
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.
@raaymax wants to fix this :)
@mmikita95 I think this one has been delivered in 0.4.0. Could you confirm ?
@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.