-
Notifications
You must be signed in to change notification settings - Fork 97
Enabling deletion mutations #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Providing __delitem__ method for StateProxy and StreamsyncState, and dict-like "remove()" API referencing the same method. Adding a "-" prefix for state keys meant to be removed at the frontend side when applying for mutated set of StateProxy. Enabling frontend to distinguish deletion mutations in ingestMutations function of core/index.ts.
| self.state[key] = value | ||
| self.apply(key) | ||
|
|
||
| def __delitem__(self, key: str) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for explicitly deleted items. But are we covering the case when someone regenerates a new StateProxy like Fabien showed in his code (when he used a dictionary comprehension)?
For example:
state["animals"] = { "cat": 1, "dog": 2 }
and then in the second run:
state["animals"] = { "dog": 3 }
Are we somehow detecting this and notifying the front end of the deletion?
I don't think it is, please let me know. Unfortunately I think a more complex and less efficient approach will be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well noticed. I'll verify it and post an update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested this case, and it seems that currently mutations for nested dictionaries are processed such that they're "nullified" each time and then reconstructed with "new" content. I'm not really sure that this behavior is efficient, but it allows to account for the case you've described. So, yeah, non-explicit removal technically works too.
Though, an interesting edge case that I've noticed to not work here is when we try to "non-explicitly remove" the whole state at once, i.e.
def very_strange_handler(state):
state = {}This simply does not produce any mutations at all, seemingly due to not invoking __setitem__ on neither StreamsyncState nor StateProxy.
UPD: Testing with the specific code that Fabien provided indeed causes some sort of problem, though I'm not sure what is it exactly. I'll continue to investigate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking into this in depth; I appreciate it. It's a complex mechanism and it's easy to miss the edge cases. Let me know whenever you have an update, feel free to take your time. Also if you want to look at it together I'm happy to.
|
As you suggested, can you please add some tests to this PR? Mainly to test deletion and the "Fabien code" for key exclusion. |
Adding "+" prepended flags for standard mutations to ensure uniformal approach on frontend and avoid unintended behaviors for keys originally named with "-" as first character. Adding reinitialization for children StateProxy instances when passing them to __setitem__ of parent StateProxy, addressing an initial_assignment desynchronization issue.
|
It was a lengthy investigation indeed, but finally a fruitful one! As it turned out, the problem with executing the code snippet Fabien provided was "desynchronization" of I.e., when we are looking at: items = state["items"].state
# This results in a dictionary containing existing StateProxy,
# with initial_assignment set to False
items = {k: v for k, v in items.items() if k != context["itemId"]}
state["items"] = items # ← __setitem__ triggered on state, passing a dictionary of StateProxy instancesWhat this amounts to is that we are given with a "newly born" # src/streamsync/core.py:267-268
# '+' flag is prepended to escaped_key as per latest update
if value.initial_assignment:
serialised_mutations[f"+{escaped_key}"] = serialised_valuethis important operation is not happening, and serialized mutations passed to frontend look like this: {"+items": null}which effectively resulted in "state nullification" bug as described by Joel here – "newly-added" The re-initialization of passed |
Child mutation flags are carried over to the beginning of the composite nested key, to ensure uniformity in mutation keys sent to frontend. Updating index.ts accordingly.
|
Finally got the opportunity to look into this in depth. Splendid work. You now understand the mechanism much better than I do! |
Providing
__delitem__method forStateProxyandStreamsyncState, and dict-likeremove()API referencing the same method. Adding a "-" prefix for state keys meant to be removed at the frontend side when applying for mutated set ofStateProxy. Enabling frontend to distinguish deletion mutations iningestMutationsfunction ofcore/index.ts.