Source code for front.data_binding
"""Data-binding primitives that connect front element values to a backing state store.
A :class:`BoundData` wraps a keyed slot in a :class:`~front.state.State` so an
element can read and write its current value through a single object. The
``ValueNotSet`` / ``Empty`` sentinels distinguish "no value yet" from a
deliberate empty value.
"""
from dataclasses import dataclass
from functools import partial
from front.state import GetterSetter, State, StateType
from i2 import Sig
from i2.util import mk_sentinel
_mk_sentinel = partial(mk_sentinel, repr_=lambda x: x.__name__, module=__name__)
ValueNotSet, Empty = map(_mk_sentinel, ["ValueNotSet", "Empty"])
[docs]
class BoundData:
"""A read/write handle on one key (``id``) of a state mapping.
``get`` returns ``ValueNotSet`` while the key is absent; ``set`` writes through
to the state. Calling the instance is the same as ``get``.
>>> state = {}
>>> bound = BoundData('x', state)
>>> bound.get()
ValueNotSet
>>> bound.set(3)
>>> bound(), state
(3, {'x': 3})
See Also:
``Binder``: makes ``BoundData`` handles on demand, as attributes.
"""
def __init__(self, id: str, state: GetterSetter):
self.id = id
self.state = State(state=state, forbidden_writes={ValueNotSet})
[docs]
def get(self):
"""Return the value stored under ``id``, or ``ValueNotSet``."""
return self.state.get(self.id, ValueNotSet)
[docs]
def set(self, value):
"""Write ``value`` under ``id`` in the state."""
self.state[self.id] = value
__call__ = get
[docs]
@dataclass
class Binder:
"""Expose keys of ``front_state`` as attributes (or items) that are ``BoundData`` handles.
Reading an unknown attribute creates a handle for that key (without writing to
the state); assigning to it creates the handle and writes the value.
>>> state = {}
>>> b = Binder(state)
>>> b.foo.get()
ValueNotSet
>>> b.foo = 42
>>> b.foo(), state
(42, {'foo': 42})
>>> b['bar'] = 'hi'
>>> state
{'foo': 42, 'bar': 'hi'}
See Also:
``front.state.mk_binder``: a descriptor-based variant with an allow-list of names.
"""
front_state: StateType
bound_data_factory = BoundData
def __post_init__(self):
sig = Sig(self.__init__)
self._reserved_keys = sig.names
def __getattr__(self, k):
self._ensure_reserved_keys()
if k not in self.__dict__["_reserved_keys"]:
setattr(self, k, Empty)
return self.__dict__[k]
def __setattr__(self, k, v):
self._ensure_reserved_keys()
if k in self.__dict__["_reserved_keys"]:
self.__dict__[k] = v
else:
bound_data = self.bound_data_factory(k, self.front_state)
if v is not Empty:
bound_data.set(v)
self.__dict__[k] = bound_data
def _ensure_reserved_keys(self):
if "_reserved_keys" not in self.__dict__:
self.__dict__["_reserved_keys"] = Sig(self.__init__).names + [
"_reserved_keys"
]
__getitem__ = __getattr__
__setitem__ = __setattr__