Update and add missing new docstrings

This commit is contained in:
miruka
2020-05-21 20:45:02 -04:00
parent cc1403974c
commit 8c9b5267e9
8 changed files with 85 additions and 5 deletions

View File

@@ -13,6 +13,8 @@ if TYPE_CHECKING:
class ModelFilter(ModelProxy):
"""Filter data from one or more source models."""
def __init__(self, sync_id: SyncId) -> None:
self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {}
self.items_changed_callbacks: List[Callable[[], None]] = []
@@ -20,6 +22,7 @@ class ModelFilter(ModelProxy):
def accept_item(self, item: "ModelItem") -> bool:
"""Return whether an item should be present or filtered out."""
return True
@@ -72,6 +75,8 @@ class ModelFilter(ModelProxy):
self,
only_if: Optional[Callable[["ModelItem"], bool]] = None,
) -> None:
"""Recheck every item to decide if they should be filtered out."""
with self._write_lock:
take_out = []
bring_back = []
@@ -103,6 +108,17 @@ class ModelFilter(ModelProxy):
class FieldSubstringFilter(ModelFilter):
"""Filter source models based on if their fields matches a string.
This is used for filter fields in QML: the user enters some text and only
items with a certain field (typically `display_name`) that contain the
words of the text (can be partial, e.g. "red" matches "red" or "tired")
will be shown.
Matching is done using "smart case": insensitive if the filter text is
all lowercase, sensitive otherwise.
"""
def __init__(self, sync_id: SyncId, fields: Collection[str]) -> None:
self.fields: Collection[str] = fields
self._filter: str = ""

View File

@@ -181,6 +181,12 @@ class Model(MutableMapping):
@contextmanager
def batch_remove(self):
"""Context manager that accumulates item removal events.
When the context manager exits, sequences of removed items are grouped
and one `ModelItemDeleted` pyotherside event is fired per sequence.
"""
try:
self._active_batch_remove_indice = []
yield None

View File

@@ -22,7 +22,11 @@ class ModelStore(UserDict):
def __missing__(self, key: SyncId) -> Model:
"""When accessing a non-existent model, create and return it."""
"""When accessing a non-existent model, create and return it.
Special models rather than a generic `Model` object may be returned
depending on the passed key.
"""
is_tuple = isinstance(key, tuple)
@@ -51,6 +55,8 @@ class ModelStore(UserDict):
async def ensure_exists_from_qml(self, sync_id: SyncId) -> None:
"""Create model if it doesn't exist. Should only be called by QML."""
if isinstance(sync_id, list): # QML can't pass tuples
sync_id = tuple(sync_id)

View File

@@ -10,6 +10,8 @@ if TYPE_CHECKING:
class ModelProxy(Model):
"""Proxies data from one or more `Model` objects."""
def __init__(self, sync_id: SyncId) -> None:
super().__init__(sync_id)
self.take_items_ownership = False
@@ -22,10 +24,20 @@ class ModelProxy(Model):
def accept_source(self, source: Model) -> bool:
"""Return whether passed `Model` should be proxied by this proxy."""
return True
def convert_item(self, item: "ModelItem") -> "ModelItem":
"""Take a source `ModelItem`, return an appropriate one for proxy.
By default, this returns the passed item unchanged.
Due to QML `ListModel` restrictions, if multiple source models
containing different subclasses of `ModelItem` are proxied,
they should be converted to a same `ModelItem`
subclass by overriding this function.
"""
return item
@@ -36,17 +48,23 @@ class ModelProxy(Model):
value: "ModelItem",
_changed_fields: Optional[Dict[str, Any]] = None,
) -> None:
"""Called when a source model item is added or changed."""
if self.accept_source(source):
value = self.convert_item(value)
self.__setitem__((source.sync_id, key), value, _changed_fields)
def source_item_deleted(self, source: Model, key) -> None:
"""Called when a source model item is removed."""
if self.accept_source(source):
del self[source.sync_id, key]
def source_cleared(self, source: Model) -> None:
"""Called when a source model is cleared."""
if self.accept_source(source):
with self.batch_remove():
for source_sync_id, key in self.copy():

View File

@@ -10,6 +10,8 @@ from .model_item import ModelItem
class AllRooms(FieldSubstringFilter):
"""Flat filtered list of all accounts and their rooms."""
def __init__(self, accounts: Model) -> None:
super().__init__(sync_id="all_rooms", fields=("display_name",))
self.items_changed_callbacks.append(self.refilter_accounts)
@@ -20,6 +22,8 @@ class AllRooms(FieldSubstringFilter):
def set_account_collapse(self, user_id: str, collapsed: bool) -> None:
"""Set whether the rooms for an account should be filtered out."""
def only_if(item):
return item.type is Room and item.for_account == user_id
@@ -74,6 +78,10 @@ class AllRooms(FieldSubstringFilter):
class MatchingAccounts(ModelFilter):
"""List of our accounts in `AllRooms` with at least one matching room if
a `filter` is set, else list of all accounts.
"""
def __init__(self, all_rooms: AllRooms) -> None:
self.all_rooms = all_rooms
self.all_rooms.items_changed_callbacks.append(self.refilter)
@@ -96,6 +104,8 @@ class MatchingAccounts(ModelFilter):
class FilteredMembers(FieldSubstringFilter):
"""Filtered list of members for a room."""
def __init__(self, user_id: str, room_id: str) -> None:
self.user_id = user_id
self.room_id = room_id