The inspiration for Spectate originally came from difficulties encountered while working with mutable data types in IPython's Traitlets. Unfortunately Traitlets does not natively allows you to track changes to mutable data types.
Now though, with Spectate, we can add this functionality to traitlets using a custom
TraitType
that can act as a base class for all mutable traits.
from spectate import mvc from traitlets import TraitType class Mutable(TraitType): """A base class for mutable traits using Spectate""" # Overwrite this in a subclass. _model_type = None # The event type observers must track to spectate changes to the model _event_type = "mutation" # You can dissallow attribute assignment to avoid discontinuities in the # knowledge observers have about the state of the model. Removing the line below # will enable attribute assignment and require observers to track 'change' # events as well as 'mutation' events in to avoid such discontinuities. __set__ = None def default(self, obj): """Create the initial model instance The value returned here will be mutated by users of the HasTraits object it is assigned to. The resulting events will be tracked in the ``callback`` defined below and distributed to event observers. """ model = self._model_type() @mvc.view(model) def callback(model, events): obj.notify_change( dict( self._make_change(model, events), name=self.name, type=self._event_type, ) ) return model def _make_change(self, model, events): """Construct a dictionary describing the change""" raise NotImplementedError()
With this in place we can then subclass our base Mutable
class and use it to create
a MutableDict
:
class MutableDict(Mutable): """A mutable dictionary trait""" _model_type = mvc.Dict def _make_change(self, model, events): old, new = {}, {} for e in events: old[e["key"]] = e["old"] new[e["key"]] = e["new"] return {"value": model, "old": old, "new": new}
An example usage of this trait would then look like:
from traitlets import HasTraits, observe class MyObject(HasTraits): mutable_dict = MutableDict() @observe("mutable_dict", type="mutation") def track_mutations_from_method(self, change): print("method observer:", change) def track_mutations_from_function(change): print("function observer:", change) my_object = MyObject() my_object.observe(track_mutations_from_function, "mutable_dict", type="mutation") my_object.mutable_dict["x"] = 1 my_object.mutable_dict.update(x=2, y=3)
method observer: {'old': {'x': Undefined}, 'new': {'x': 1}, 'name': 'mutable_dict', 'type': 'mutation'}
function observer: {'old': {'x': Undefined}, 'new': {'x': 1}, 'name': 'mutable_dict', 'type': 'mutation'}
method observer: {'old': {'x': 1, 'y': Undefined}, 'new': {'x': 2, 'y': 3}, 'name': 'mutable_dict', 'type': 'mutation'}
function observer: {'old': {'x': 1, 'y': Undefined}, 'new': {'x': 2, 'y': 3}, 'name': 'mutable_dict', 'type': 'mutation'}