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

@ -1,5 +1,8 @@
# TODO
- pdb
- remove await_model_item
- uplaod/download use kwargs
- highlight messages being responded to
- highlight messages with mention
- add room members loading indicator
@ -9,7 +12,6 @@
it won't be visible in timeline no matter what the user config is
- fix: there are rooms without messages on first sync
- update docstrings
- update flatpak nio required version
- final test

View File

@ -45,7 +45,7 @@ class Backend:
synchronized between the Python backend and the QML UI.
The models should only ever be modified from the backend.
If a non-existent key is accessed, it is creating with an
If a non-existent key is accessed, it is created and an
associated `Model` and returned.
The mapping keys are the `Model`'s synchronization ID,
@ -66,6 +66,17 @@ class Backend:
- `("<user_id>", "<room_id>", "events")`: state events and messages
in the room `room_id` that our account `user_id` is part of.
Special models:
- `"all_rooms"`: See `models.special_models.AllRooms` docstring
- `"matching_accounts"`
See `models.special_models.MatchingAccounts` docstring
- `("<user_id>", "<room_id>", "filtered_members")`:
See `models.special_models.FilteredMembers` docstring
clients: A `{user_id: MatrixClient}` dict for the logged-in clients
we managed. Every client is logged to one matrix account.
@ -321,6 +332,11 @@ class Backend:
async def set_substring_filter(self, model_id: SyncId, value: str) -> None:
"""Set a FieldSubstringFilter model's filter property.
This should only be called from QML.
"""
if isinstance(model_id, list): # QML can't pass tuples
model_id = tuple(model_id)
@ -333,4 +349,8 @@ class Backend:
async def set_account_collapse(self, user_id: str, collapse: bool) -> None:
"""Call `set_account_collapse()` on the `all_rooms` model.
This should only be called from QML.
"""
self.models["all_rooms"].set_account_collapse(user_id, collapse)

View File

@ -1242,7 +1242,7 @@ class MatrixClient(nio.AsyncClient):
left: bool = False,
force_register_members: bool = False,
) -> None:
"""Register a `nio.MatrixRoom` as a `Room` object in our model."""
"""Register/update a `nio.MatrixRoom` as a `models.items.Room`."""
# Add room
inviter = getattr(room, "inviter", "") or ""
@ -1310,6 +1310,7 @@ class MatrixClient(nio.AsyncClient):
async def add_member(self, room: nio.MatrixRoom, user_id: str) -> None:
"""Register/update a room member into our models."""
member = room.users[user_id]
self.models[self.user_id, room.room_id, "members"][user_id] = Member(
@ -1328,6 +1329,7 @@ class MatrixClient(nio.AsyncClient):
async def remove_member(self, room: nio.MatrixRoom, user_id: str) -> None:
"""Remove a room member from our models."""
self.models[self.user_id, room.room_id, "members"].pop(user_id, None)
HTML.rooms_user_id_names[room.room_id].pop(user_id, None)
@ -1403,7 +1405,7 @@ class MatrixClient(nio.AsyncClient):
override_fetch_profile: Optional[bool] = None,
**fields,
) -> None:
"""Register a `nio.Event` as a `Event` object in our model."""
"""Register/update a `nio.Event` as a `models.items.Event` object."""
await self.register_nio_room(room)

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