rmorshea / spectate

Observe the evolution of mutable data types like lists, dicts, and sets.

Home Page:https://python-spectate.readthedocs.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to use in class (mimic traitlets)

fitoprincipe opened this issue · comments

Hi! I finally decided to have a try with spectate, but I am having trouble migrating from traitlets.

This is what I'd do in traitlets

from traitlets import List, HasTraits, observe
class TestTrait(HasTraits):
    attr = List()
    number = 1
    
    @observe('attr')
    def _ob_attr(self, change):
        print('number is {}'.format(self.number))
        print(change)

But of course this do not work with spectate

from spectate import mvc
class TestSpec(object):
    attr = mvc.List()
    number = 1
    
    @mvc.view(attr)
    def _ob_attr(attr, change):
        # print('number is {}'.format(self.number)) # can't access self
        print(change)

I read this but couldn't find the right path.

Thanks

@fitoprincipe sorry this took so long for me to get to.

Spectate isn't really meant to replicate the behavior of Traitlets. Instead it's mostly focused on tracking changes to mutable data types, so there's not a great way to do that with Spectate.

With that said, Spectate can actually be used with Traitlets. I've made a PR to that effect:

jupyter-widgets/traittypes#36

Here's a working implementation:

from spectate import mvc
from traitlets import TraitType, HasTraits, observe


class Mutable(TraitType):
    """A base class for mutable traits using Spectate"""

    _model_type = None
    _event_type = None

    def instance_init(self, obj):
        default = self._model_type()

        @mvc.view(default)
        def callback(default, events):
            change = dict(
                self._make_change(events),
                name=self.name,
                type=self._event_type,
            )
            obj.notify_change(change)

        setattr(obj, self.name, default)

    def _make_change(self, events):
        raise NotImplementedError()


class MutableDict(Mutable):
    """A mutable dictionary trait"""

    _model_type = mvc.Dict
    _event_type = "item"

    def _make_change(self, events):
        change = {"old": {}, "new": {}}
        for e in events:
            change["new"][e.key] = e.new
            change["old"][e.key] = e.old
        return change

Here's a HasTraits class that uses the mutable dict trait:

class MyHasTraitsClass(HasTraits):
    
    attr = MutableDict()
    
    @observe("attr", type="item")
    def _on_attr_change(self, change):
        print(change)

Here's an example usage:

>>> mhtc = MyHasTraitsClass()
>>> mhtc.attr["a"] = 1
{'old': {'a': Undefined}, 'new': {'a': 1}, 'name': 'attr', 'type': 'item'}

Thank you @rmorshea ! I didn't have time to test it, but I will as soon as I have some =)

Hey @rmorshea just wanted to let you know that I got it working :) Thanks for your work here!

My implementation was a little different though, because I'm using it with jupyter notebooks.

I'll leave this open until I close #37

Here's a working implementation:

from spectate import mvc
from traitlets import TraitType, HasTraits, observe


class Mutable(TraitType):
    """A base class for mutable traits using Spectate"""

    _model_type = None
    _event_type = None

    def instance_init(self, obj):
        default = self._model_type()

        @mvc.view(default)
        def callback(default, events):
            change = dict(
                self._make_change(events),
                name=self.name,
                type=self._event_type,
            )
            obj.notify_change(change)

        setattr(obj, self.name, default)

    def _make_change(self, events):
        raise NotImplementedError()


class MutableDict(Mutable):
    """A mutable dictionary trait"""

    _model_type = mvc.Dict
    _event_type = "item"

    def _make_change(self, events):
        change = {"old": {}, "new": {}}
        for e in events:
            change["new"][e.key] = e.new
            change["old"][e.key] = e.old
        return change

Here's a HasTraits class that uses the mutable dict trait:

class MyHasTraitsClass(HasTraits):
    
    attr = MutableDict()
    
    @observe("attr", type="item")
    def _on_attr_change(self, change):
        print(change)

Here's an example usage:

>>> mhtc = MyHasTraitsClass()
>>> mhtc.attr["a"] = 1
{'old': {'a': Undefined}, 'new': {'a': 1}, 'name': 'attr', 'type': 'item'}

How would you go about doing this if you wanted to track changes to a list traitlet rather than dict trailet? In other words, how
would you implement a MutableList traitlet using spectate?

I think @marimeireles's suggestion is accurate. @trbedwards, assuming the same implementation style, you'll just want to make a subclass of Mutable whose _model_type is an mvc.List

@trbedwards let me know if this section of the documentation could use some improvement: https://python-spectate.readthedocs.io/en/latest/usage/spectate-in-traitlets.html