digitalaudioswitch/statetree.py

78 lines
2.2 KiB
Python

class StateTree:
"""A dictionary-like object that tracks when values have been changed."""
def __init__(self, dictionary: dict = None, parent: "StateTree" = None) -> None:
"""Create a new state tree.
If a dictionary is supplied, its values will be initialized with it and
the tree will be marked as clean.
If a parent is supplied, the parent will be marked as dirty when this
tree is modified.
"""
self._dictionary = dictionary if dictionary else dict()
self._parent = parent
self._changed = False
def dirty(self):
"""Mark the tree as dirty.
This is done automatically whenever an item is updated.
"""
self._changed = True
if self._parent:
self._parent.dirty()
def clean(self):
"""Mark the tree as clean.
Use this method to reset the changed status of the tree once after
you've reacted to it being updated.
"""
self._changed = False
@property
def changed(self):
"""Returns whether the tree has been modified since the last time it was
marked as clean."""
return self._changed
@property
def dictionary(self):
"""Returns the underlying dictionary."""
return self._dictionary
def __getitem__(self, *args, **kwargs):
"""Get the value stored in a key in the tree.
If the value is a dictionary, returns a StateTree object instead that
will notify the parent if a change is made.
"""
o = self._dictionary.__getitem__(*args, **kwargs)
if isinstance(o, dict):
return StateTree(o, parent=self)
else:
return o
def __setitem__(self, key, value):
"""Update the value of a key in the tree.
Marks the tree as changed if the key is new or the new value differs
from the current value.
"""
if key not in self._dictionary or value != self._dictionary[key]:
self.dirty()
self._dictionary[key] = value
def __repr__(self):
return "<StateTree{}{} {}>".format(
"^" if self._parent else "",
"*" if self._changed else "",
repr(self._dictionary),
)