Document model.py

This commit is contained in:
miruka 2019-12-18 16:41:51 -04:00
parent 1f41e2ffaa
commit e6541cd767
2 changed files with 40 additions and 1 deletions

View File

@ -9,6 +9,24 @@ from .model_item import ModelItem
class Model(MutableMapping): class Model(MutableMapping):
"""A mapping of `{identifier: ModelItem}` synced between Python & QML.
From the Python side, the model is usable like a normal dict of
`ModelItem` subclass objects.
Different types of `ModelItem` must not be mixed in the same model.
When items are added, changed or removed from the model, a synchronization
with QML is scheduled.
The model will synchronize with QML no more than every 0.25s, for
performance reasons; though it is possible to request an instant sync
via `sync_now()` for certain cases when this delay is unacceptable.
Model data is sent to QML using a `ModelUpdated` event from the
`pyotherside_events` module.
The data is a list of serialized `ModelItem` dicts, as expected
by QML for components like `ListView`.
"""
def __init__(self, sync_id: SyncId) -> None: def __init__(self, sync_id: SyncId) -> None:
self.sync_id: SyncId = sync_id self.sync_id: SyncId = sync_id
self._data: Dict[Any, ModelItem] = {} self._data: Dict[Any, ModelItem] = {}
@ -20,6 +38,8 @@ class Model(MutableMapping):
def __repr__(self) -> str: def __repr__(self) -> str:
"""Provide a full representation of the model and its content."""
try: try:
from pprintpp import pformat from pprintpp import pformat
except ImportError: except ImportError:
@ -36,6 +56,8 @@ class Model(MutableMapping):
def __str__(self) -> str: def __str__(self) -> str:
"""Provide a short "<sync_id>: <num> items" representation."""
if isinstance(self.sync_id, tuple): if isinstance(self.sync_id, tuple):
reprs = tuple(repr(s) for s in self.sync_id[1:]) reprs = tuple(repr(s) for s in self.sync_id[1:])
sid = ", ".join((self.sync_id[0].__name__, *reprs)) sid = ", ".join((self.sync_id[0].__name__, *reprs))
@ -51,6 +73,16 @@ class Model(MutableMapping):
def __setitem__(self, key, value: ModelItem) -> None: def __setitem__(self, key, value: ModelItem) -> None:
"""Merge new item with an existing one if possible, else add it.
If an existing item with the passed `key` is found, its fields will be
updated with the passed `ModelItem`'s fields.
In other cases, the item is simply added to the model.
This also sets the `ModelItem.parent_model` hidden attribute on the
passed item.
"""
new = value new = value
if key in self: if key in self:
@ -89,6 +121,8 @@ class Model(MutableMapping):
def _sync_loop(self) -> None: def _sync_loop(self) -> None:
"""Loop to synchronize model when needed with a cooldown of 0.25s."""
while True: while True:
time.sleep(0.25) time.sleep(0.25)
@ -99,13 +133,18 @@ class Model(MutableMapping):
def sync_now(self) -> None: def sync_now(self) -> None:
"""Trigger a model synchronization right now. Use with precaution."""
ModelUpdated(self.sync_id, self.serialized()) ModelUpdated(self.sync_id, self.serialized())
self._changed = False self._changed = False
def serialized(self) -> List[Dict[str, Any]]: def serialized(self) -> List[Dict[str, Any]]:
"""Return serialized model content as a list of dict for QML."""
return [item.serialized for item in sorted(self._data.values())] return [item.serialized for item in sorted(self._data.values())]
def __lt__(self, other: "Model") -> bool: def __lt__(self, other: "Model") -> bool:
"""Sort `Model` objects lexically by `sync_id`."""
return str(self.sync_id) < str(other.sync_id) return str(self.sync_id) < str(other.sync_id)

View File

@ -49,7 +49,7 @@ class CoroutineDone(PyOtherSideEvent):
@dataclass @dataclass
class ModelUpdated(PyOtherSideEvent): class ModelUpdated(PyOtherSideEvent):
"""Indicate that a backend model's data changed.""" """Indicate that a backend `Model`'s data changed."""
sync_id: SyncId = field() sync_id: SyncId = field()
data: List[Dict[str, Any]] = field() data: List[Dict[str, Any]] = field()