Update and add missing new docstrings
This commit is contained in:
parent
cc1403974c
commit
8c9b5267e9
4
TODO.md
4
TODO.md
|
@ -1,5 +1,8 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
- pdb
|
||||||
|
- remove await_model_item
|
||||||
|
- uplaod/download use kwargs
|
||||||
- highlight messages being responded to
|
- highlight messages being responded to
|
||||||
- highlight messages with mention
|
- highlight messages with mention
|
||||||
- add room members loading indicator
|
- add room members loading indicator
|
||||||
|
@ -9,7 +12,6 @@
|
||||||
it won't be visible in timeline no matter what the user config is
|
it won't be visible in timeline no matter what the user config is
|
||||||
- fix: there are rooms without messages on first sync
|
- fix: there are rooms without messages on first sync
|
||||||
|
|
||||||
- update docstrings
|
|
||||||
- update flatpak nio required version
|
- update flatpak nio required version
|
||||||
- final test
|
- final test
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Backend:
|
||||||
synchronized between the Python backend and the QML UI.
|
synchronized between the Python backend and the QML UI.
|
||||||
The models should only ever be modified from the backend.
|
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.
|
associated `Model` and returned.
|
||||||
|
|
||||||
The mapping keys are the `Model`'s synchronization ID,
|
The mapping keys are the `Model`'s synchronization ID,
|
||||||
|
@ -66,6 +66,17 @@ class Backend:
|
||||||
- `("<user_id>", "<room_id>", "events")`: state events and messages
|
- `("<user_id>", "<room_id>", "events")`: state events and messages
|
||||||
in the room `room_id` that our account `user_id` is part of.
|
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
|
clients: A `{user_id: MatrixClient}` dict for the logged-in clients
|
||||||
we managed. Every client is logged to one matrix account.
|
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:
|
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
|
if isinstance(model_id, list): # QML can't pass tuples
|
||||||
model_id = tuple(model_id)
|
model_id = tuple(model_id)
|
||||||
|
|
||||||
|
@ -333,4 +349,8 @@ class Backend:
|
||||||
|
|
||||||
|
|
||||||
async def set_account_collapse(self, user_id: str, collapse: bool) -> None:
|
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)
|
self.models["all_rooms"].set_account_collapse(user_id, collapse)
|
||||||
|
|
|
@ -1242,7 +1242,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
left: bool = False,
|
left: bool = False,
|
||||||
force_register_members: bool = False,
|
force_register_members: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register a `nio.MatrixRoom` as a `Room` object in our model."""
|
"""Register/update a `nio.MatrixRoom` as a `models.items.Room`."""
|
||||||
|
|
||||||
# Add room
|
# Add room
|
||||||
inviter = getattr(room, "inviter", "") or ""
|
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:
|
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]
|
member = room.users[user_id]
|
||||||
|
|
||||||
self.models[self.user_id, room.room_id, "members"][user_id] = Member(
|
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:
|
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)
|
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)
|
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,
|
override_fetch_profile: Optional[bool] = None,
|
||||||
**fields,
|
**fields,
|
||||||
) -> None:
|
) -> 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)
|
await self.register_nio_room(room)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class ModelFilter(ModelProxy):
|
class ModelFilter(ModelProxy):
|
||||||
|
"""Filter data from one or more source models."""
|
||||||
|
|
||||||
def __init__(self, sync_id: SyncId) -> None:
|
def __init__(self, sync_id: SyncId) -> None:
|
||||||
self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {}
|
self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {}
|
||||||
self.items_changed_callbacks: List[Callable[[], None]] = []
|
self.items_changed_callbacks: List[Callable[[], None]] = []
|
||||||
|
@ -20,6 +22,7 @@ class ModelFilter(ModelProxy):
|
||||||
|
|
||||||
|
|
||||||
def accept_item(self, item: "ModelItem") -> bool:
|
def accept_item(self, item: "ModelItem") -> bool:
|
||||||
|
"""Return whether an item should be present or filtered out."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,6 +75,8 @@ class ModelFilter(ModelProxy):
|
||||||
self,
|
self,
|
||||||
only_if: Optional[Callable[["ModelItem"], bool]] = None,
|
only_if: Optional[Callable[["ModelItem"], bool]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Recheck every item to decide if they should be filtered out."""
|
||||||
|
|
||||||
with self._write_lock:
|
with self._write_lock:
|
||||||
take_out = []
|
take_out = []
|
||||||
bring_back = []
|
bring_back = []
|
||||||
|
@ -103,6 +108,17 @@ class ModelFilter(ModelProxy):
|
||||||
|
|
||||||
|
|
||||||
class FieldSubstringFilter(ModelFilter):
|
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:
|
def __init__(self, sync_id: SyncId, fields: Collection[str]) -> None:
|
||||||
self.fields: Collection[str] = fields
|
self.fields: Collection[str] = fields
|
||||||
self._filter: str = ""
|
self._filter: str = ""
|
||||||
|
|
|
@ -181,6 +181,12 @@ class Model(MutableMapping):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def batch_remove(self):
|
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:
|
try:
|
||||||
self._active_batch_remove_indice = []
|
self._active_batch_remove_indice = []
|
||||||
yield None
|
yield None
|
||||||
|
|
|
@ -22,7 +22,11 @@ class ModelStore(UserDict):
|
||||||
|
|
||||||
|
|
||||||
def __missing__(self, key: SyncId) -> Model:
|
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)
|
is_tuple = isinstance(key, tuple)
|
||||||
|
|
||||||
|
@ -51,6 +55,8 @@ class ModelStore(UserDict):
|
||||||
|
|
||||||
|
|
||||||
async def ensure_exists_from_qml(self, sync_id: SyncId) -> None:
|
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
|
if isinstance(sync_id, list): # QML can't pass tuples
|
||||||
sync_id = tuple(sync_id)
|
sync_id = tuple(sync_id)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class ModelProxy(Model):
|
class ModelProxy(Model):
|
||||||
|
"""Proxies data from one or more `Model` objects."""
|
||||||
|
|
||||||
def __init__(self, sync_id: SyncId) -> None:
|
def __init__(self, sync_id: SyncId) -> None:
|
||||||
super().__init__(sync_id)
|
super().__init__(sync_id)
|
||||||
self.take_items_ownership = False
|
self.take_items_ownership = False
|
||||||
|
@ -22,10 +24,20 @@ class ModelProxy(Model):
|
||||||
|
|
||||||
|
|
||||||
def accept_source(self, source: Model) -> bool:
|
def accept_source(self, source: Model) -> bool:
|
||||||
|
"""Return whether passed `Model` should be proxied by this proxy."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def convert_item(self, item: "ModelItem") -> "ModelItem":
|
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
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,17 +48,23 @@ class ModelProxy(Model):
|
||||||
value: "ModelItem",
|
value: "ModelItem",
|
||||||
_changed_fields: Optional[Dict[str, Any]] = None,
|
_changed_fields: Optional[Dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Called when a source model item is added or changed."""
|
||||||
|
|
||||||
if self.accept_source(source):
|
if self.accept_source(source):
|
||||||
value = self.convert_item(value)
|
value = self.convert_item(value)
|
||||||
self.__setitem__((source.sync_id, key), value, _changed_fields)
|
self.__setitem__((source.sync_id, key), value, _changed_fields)
|
||||||
|
|
||||||
|
|
||||||
def source_item_deleted(self, source: Model, key) -> None:
|
def source_item_deleted(self, source: Model, key) -> None:
|
||||||
|
"""Called when a source model item is removed."""
|
||||||
|
|
||||||
if self.accept_source(source):
|
if self.accept_source(source):
|
||||||
del self[source.sync_id, key]
|
del self[source.sync_id, key]
|
||||||
|
|
||||||
|
|
||||||
def source_cleared(self, source: Model) -> None:
|
def source_cleared(self, source: Model) -> None:
|
||||||
|
"""Called when a source model is cleared."""
|
||||||
|
|
||||||
if self.accept_source(source):
|
if self.accept_source(source):
|
||||||
with self.batch_remove():
|
with self.batch_remove():
|
||||||
for source_sync_id, key in self.copy():
|
for source_sync_id, key in self.copy():
|
||||||
|
|
|
@ -10,6 +10,8 @@ from .model_item import ModelItem
|
||||||
|
|
||||||
|
|
||||||
class AllRooms(FieldSubstringFilter):
|
class AllRooms(FieldSubstringFilter):
|
||||||
|
"""Flat filtered list of all accounts and their rooms."""
|
||||||
|
|
||||||
def __init__(self, accounts: Model) -> None:
|
def __init__(self, accounts: Model) -> None:
|
||||||
super().__init__(sync_id="all_rooms", fields=("display_name",))
|
super().__init__(sync_id="all_rooms", fields=("display_name",))
|
||||||
self.items_changed_callbacks.append(self.refilter_accounts)
|
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:
|
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):
|
def only_if(item):
|
||||||
return item.type is Room and item.for_account == user_id
|
return item.type is Room and item.for_account == user_id
|
||||||
|
|
||||||
|
@ -74,6 +78,10 @@ class AllRooms(FieldSubstringFilter):
|
||||||
|
|
||||||
|
|
||||||
class MatchingAccounts(ModelFilter):
|
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:
|
def __init__(self, all_rooms: AllRooms) -> None:
|
||||||
self.all_rooms = all_rooms
|
self.all_rooms = all_rooms
|
||||||
self.all_rooms.items_changed_callbacks.append(self.refilter)
|
self.all_rooms.items_changed_callbacks.append(self.refilter)
|
||||||
|
@ -96,6 +104,8 @@ class MatchingAccounts(ModelFilter):
|
||||||
|
|
||||||
|
|
||||||
class FilteredMembers(FieldSubstringFilter):
|
class FilteredMembers(FieldSubstringFilter):
|
||||||
|
"""Filtered list of members for a room."""
|
||||||
|
|
||||||
def __init__(self, user_id: str, room_id: str) -> None:
|
def __init__(self, user_id: str, room_id: str) -> None:
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.room_id = room_id
|
self.room_id = room_id
|
||||||
|
|
Loading…
Reference in New Issue
Block a user