diff --git a/src/backend/models/__init__.py b/src/backend/models/__init__.py index 6ebb92fb..f4b9fac9 100644 --- a/src/backend/models/__init__.py +++ b/src/backend/models/__init__.py @@ -1,3 +1,5 @@ +"""Provide classes related to data models shared between Python and QML.""" + from typing import Tuple, Type, Union from .model_item import ModelItem diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 8d8d3122..b7963845 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -1,3 +1,5 @@ +"""`ModelItem` subclasses definitions.""" + import asyncio import re from dataclasses import dataclass, field @@ -19,6 +21,8 @@ OptionalExceptionType = Union[Type[None], Type[Exception]] @dataclass class Account(ModelItem): + """A logged in matrix account.""" + user_id: str = field() display_name: str = "" avatar_url: str = "" @@ -26,17 +30,21 @@ class Account(ModelItem): profile_updated: Optional[datetime] = None def __lt__(self, other: "Account") -> bool: + """Sort by display name or user ID.""" name = self.display_name or self.user_id[1:] other_name = other.display_name or other.user_id[1:] return name < other_name @property def filter_string(self) -> str: + """Filter based on display name.""" return self.display_name @dataclass class Room(ModelItem): + """A matrix room we are invited to, are or were member of.""" + room_id: str = field() given_name: str = "" display_name: str = "" @@ -66,8 +74,13 @@ class Room(ModelItem): last_event: Optional[Dict[str, Any]] = field(default=None, repr=False) def __lt__(self, other: "Room") -> bool: - # Order: Invited rooms > joined rooms > left rooms. - # Within these categories, sort by date then by name. + """Sort by join state, then descending last event date, then name. + + Invited rooms are first, then joined rooms, then left rooms. + Within these categories, sort by last event date (room with recent + messages are first), then by display names. + """ + # Left rooms may still have an inviter_id, so check left first. return ( self.left, @@ -91,6 +104,8 @@ class Room(ModelItem): @property def filter_string(self) -> str: + """Filter based on room display name, topic, and last event content.""" + return " ".join(( self.display_name, self.topic, @@ -101,6 +116,8 @@ class Room(ModelItem): @dataclass class Member(ModelItem): + """A member in a matrix room.""" + user_id: str = field() display_name: str = "" avatar_url: str = "" @@ -109,8 +126,8 @@ class Member(ModelItem): invited: bool = False def __lt__(self, other: "Member") -> bool: - # Sort by name, but have members with higher power-level first and - # invited-but-not-joined members last + """Sort by power level, then by display name/user ID.""" + name = (self.display_name or self.user_id[1:]).lower() other_name = (other.display_name or other.user_id[1:]).lower() @@ -123,10 +140,13 @@ class Member(ModelItem): @property def filter_string(self) -> str: + """Filter members based on display name.""" return self.display_name class UploadStatus(AutoStrEnum): + """Enum describing the status of an upload operation.""" + Uploading = auto() Caching = auto() Error = auto() @@ -134,6 +154,8 @@ class UploadStatus(AutoStrEnum): @dataclass class Upload(ModelItem): + """Represent a running or failed file upload operation.""" + uuid: UUID = field() task: asyncio.Task = field() monitor: nio.TransferMonitor = field() @@ -152,11 +174,14 @@ class Upload(ModelItem): def __lt__(self, other: "Upload") -> bool: - # Sort from newest upload to oldest. + """Sort by the start date, from newest upload to oldest.""" + return self.start_date > other.start_date class TypeSpecifier(AutoStrEnum): + """Enum providing clarification of purpose for some matrix events.""" + none = auto() profile_change = auto() membership_change = auto() @@ -164,6 +189,8 @@ class TypeSpecifier(AutoStrEnum): @dataclass class Event(ModelItem): + """A matrix state event or message.""" + source: Optional[nio.Event] = field() client_id: str = field() event_id: str = field() @@ -204,11 +231,14 @@ class Event(ModelItem): def __lt__(self, other: "Event") -> bool: - # Sort events from newest to oldest. return True means return False. + """Sort by date in descending order, from newest to oldest.""" + return self.date > other.date @property def event_type(self) -> Type: + """Type of the source nio event used to create this `Event`.""" + if self.local_event_type: return self.local_event_type @@ -216,6 +246,8 @@ class Event(ModelItem): @property def links(self) -> List[str]: + """List of URLs (`` tags) present in the event content.""" + urls: List[str] = [] if self.content.strip(): @@ -229,6 +261,8 @@ class Event(ModelItem): @dataclass class Device(ModelItem): + """A matrix user's device. This class is currently unused.""" + device_id: str = field() ed25519_key: str = field() trusted: bool = False diff --git a/src/backend/models/model_item.py b/src/backend/models/model_item.py index 4b68fd04..313fe638 100644 --- a/src/backend/models/model_item.py +++ b/src/backend/models/model_item.py @@ -4,6 +4,19 @@ from ..utils import serialize_value_for_qml class ModelItem: + """Base class for items stored inside a `Model`. + + This class must be subclassed and not used directly. + All subclasses must be dataclasses. + + Subclasses are also expected to implement `__lt__()`, + to provide support for comparisons with the `<`, `>`, `<=`, `=>` operators + and thus allow a `Model` to sort its `ModelItem`s. + + They may also implement a `filter_string` property, that will be used + for filtering from the UI. + """ + def __new__(cls, *_args, **_kwargs) -> "ModelItem": from .model import Model cls.parent_model: Optional[Model] = None @@ -11,6 +24,8 @@ class ModelItem: def __setattr__(self, name: str, value) -> None: + """If this item is in a `Model`, alert it of attribute changes.""" + super().__setattr__(name, value) if name != "parent_model" and self.parent_model is not None: @@ -24,6 +39,8 @@ class ModelItem: @property def serialized(self) -> Dict[str, Any]: + """Return this item as a dict ready to be passed to QML.""" + return { name: serialize_value_for_qml(getattr(self, name)) for name in dir(self)