pyapp-kit / psygnal

Python observer pattern (callback/event system). Modeled after Qt Signals & Slots (but independent of Qt)
https://psygnal.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
84 stars 13 forks source link

Any way to make this work with mutable data structures? #252

Open zulrang opened 9 months ago

zulrang commented 9 months ago

Description

After connecting events to mutable types, I would expect that events are called when they are mutated.

What I Did

from psygnal import EventedModel

class MyModel(EventedModel):
    data: dict = {}
    values: list[int]

model = MyModel(data={'foo': 'bar'}, values=[1, 2, 3])
model.events.data.connect(lambda v: print(f"updated data: {v}"))
model.events.values.connect(lambda v: print(f"updated values: {v}"))

model.data['test'] = 'fail'
model.values.append(4)

This doesn't print anything.

tlambert03 commented 9 months ago

you would need to use an evented container like psygnal.containers.EventedList. The reason is that when you set the attribute of a field directly model.field = thing, it goes through the __setattr__ of the model. When you use model.values.append (or any other thing that calls model.values.__setitem__) then there's no way for the evented model to know about it.

You would need to do something like this:

from psygnal import EventedModel
from psygnal.containers import EventedList

class MyModel(EventedModel):
    data: dict = {}
    values: EventedList[int]

however, on a quick try, I'm noticing a couple issues at the moment with using an unmodified EventedList as a hint for a pydantic model (both v1 and v2). So, let me have a closer look tomorrow.

tlambert03 commented 9 months ago

also related... even once it does work as a field (which it should)... you'll have the issue of nested events. That is, since an evented list has many events (not just a single change event), it's not immediately clear how one should access them on the model.events field itself. This was being addressed in https://github.com/pyapp-kit/psygnal/pull/169 ... but it hasn't seen action in a while. Let me know if you specifically need the nesting for your use case, or if using model.values.events... would be sufficient in this case

ndxmrb commented 7 months ago

After this has seen some work but no answer from OP so far, I thought I'd chime in: I would love to see #169 coming back to life, because it could enable undo/redo implementations by using the memento or state-diff patterns. Then use EventedModel.update() for in-place updates from the saved states.

tlambert03 commented 7 months ago

thanks for chiming in @ndxmrb, it definitely helps to know what features people would like to see implemented! I'll look at reviving that PR, as I'm also still interested in using it myself