Document model items

This commit is contained in:
miruka 2019-12-18 15:00:34 -04:00
parent 934d6a79a2
commit 5f1044e96a
3 changed files with 59 additions and 6 deletions

View File

@ -1,3 +1,5 @@
"""Provide classes related to data models shared between Python and QML."""
from typing import Tuple, Type, Union from typing import Tuple, Type, Union
from .model_item import ModelItem from .model_item import ModelItem

View File

@ -1,3 +1,5 @@
"""`ModelItem` subclasses definitions."""
import asyncio import asyncio
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -19,6 +21,8 @@ OptionalExceptionType = Union[Type[None], Type[Exception]]
@dataclass @dataclass
class Account(ModelItem): class Account(ModelItem):
"""A logged in matrix account."""
user_id: str = field() user_id: str = field()
display_name: str = "" display_name: str = ""
avatar_url: str = "" avatar_url: str = ""
@ -26,17 +30,21 @@ class Account(ModelItem):
profile_updated: Optional[datetime] = None profile_updated: Optional[datetime] = None
def __lt__(self, other: "Account") -> bool: def __lt__(self, other: "Account") -> bool:
"""Sort by display name or user ID."""
name = self.display_name or self.user_id[1:] name = self.display_name or self.user_id[1:]
other_name = other.display_name or other.user_id[1:] other_name = other.display_name or other.user_id[1:]
return name < other_name return name < other_name
@property @property
def filter_string(self) -> str: def filter_string(self) -> str:
"""Filter based on display name."""
return self.display_name return self.display_name
@dataclass @dataclass
class Room(ModelItem): class Room(ModelItem):
"""A matrix room we are invited to, are or were member of."""
room_id: str = field() room_id: str = field()
given_name: str = "" given_name: str = ""
display_name: str = "" display_name: str = ""
@ -66,8 +74,13 @@ class Room(ModelItem):
last_event: Optional[Dict[str, Any]] = field(default=None, repr=False) last_event: Optional[Dict[str, Any]] = field(default=None, repr=False)
def __lt__(self, other: "Room") -> bool: def __lt__(self, other: "Room") -> bool:
# Order: Invited rooms > joined rooms > left rooms. """Sort by join state, then descending last event date, then name.
# Within these categories, sort by date then by 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. # Left rooms may still have an inviter_id, so check left first.
return ( return (
self.left, self.left,
@ -91,6 +104,8 @@ class Room(ModelItem):
@property @property
def filter_string(self) -> str: def filter_string(self) -> str:
"""Filter based on room display name, topic, and last event content."""
return " ".join(( return " ".join((
self.display_name, self.display_name,
self.topic, self.topic,
@ -101,6 +116,8 @@ class Room(ModelItem):
@dataclass @dataclass
class Member(ModelItem): class Member(ModelItem):
"""A member in a matrix room."""
user_id: str = field() user_id: str = field()
display_name: str = "" display_name: str = ""
avatar_url: str = "" avatar_url: str = ""
@ -109,8 +126,8 @@ class Member(ModelItem):
invited: bool = False invited: bool = False
def __lt__(self, other: "Member") -> bool: def __lt__(self, other: "Member") -> bool:
# Sort by name, but have members with higher power-level first and """Sort by power level, then by display name/user ID."""
# invited-but-not-joined members last
name = (self.display_name or self.user_id[1:]).lower() name = (self.display_name or self.user_id[1:]).lower()
other_name = (other.display_name or other.user_id[1:]).lower() other_name = (other.display_name or other.user_id[1:]).lower()
@ -123,10 +140,13 @@ class Member(ModelItem):
@property @property
def filter_string(self) -> str: def filter_string(self) -> str:
"""Filter members based on display name."""
return self.display_name return self.display_name
class UploadStatus(AutoStrEnum): class UploadStatus(AutoStrEnum):
"""Enum describing the status of an upload operation."""
Uploading = auto() Uploading = auto()
Caching = auto() Caching = auto()
Error = auto() Error = auto()
@ -134,6 +154,8 @@ class UploadStatus(AutoStrEnum):
@dataclass @dataclass
class Upload(ModelItem): class Upload(ModelItem):
"""Represent a running or failed file upload operation."""
uuid: UUID = field() uuid: UUID = field()
task: asyncio.Task = field() task: asyncio.Task = field()
monitor: nio.TransferMonitor = field() monitor: nio.TransferMonitor = field()
@ -152,11 +174,14 @@ class Upload(ModelItem):
def __lt__(self, other: "Upload") -> bool: 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 return self.start_date > other.start_date
class TypeSpecifier(AutoStrEnum): class TypeSpecifier(AutoStrEnum):
"""Enum providing clarification of purpose for some matrix events."""
none = auto() none = auto()
profile_change = auto() profile_change = auto()
membership_change = auto() membership_change = auto()
@ -164,6 +189,8 @@ class TypeSpecifier(AutoStrEnum):
@dataclass @dataclass
class Event(ModelItem): class Event(ModelItem):
"""A matrix state event or message."""
source: Optional[nio.Event] = field() source: Optional[nio.Event] = field()
client_id: str = field() client_id: str = field()
event_id: str = field() event_id: str = field()
@ -204,11 +231,14 @@ class Event(ModelItem):
def __lt__(self, other: "Event") -> bool: 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 return self.date > other.date
@property @property
def event_type(self) -> Type: def event_type(self) -> Type:
"""Type of the source nio event used to create this `Event`."""
if self.local_event_type: if self.local_event_type:
return self.local_event_type return self.local_event_type
@ -216,6 +246,8 @@ class Event(ModelItem):
@property @property
def links(self) -> List[str]: def links(self) -> List[str]:
"""List of URLs (`<a href=...>` tags) present in the event content."""
urls: List[str] = [] urls: List[str] = []
if self.content.strip(): if self.content.strip():
@ -229,6 +261,8 @@ class Event(ModelItem):
@dataclass @dataclass
class Device(ModelItem): class Device(ModelItem):
"""A matrix user's device. This class is currently unused."""
device_id: str = field() device_id: str = field()
ed25519_key: str = field() ed25519_key: str = field()
trusted: bool = False trusted: bool = False

View File

@ -4,6 +4,19 @@ from ..utils import serialize_value_for_qml
class ModelItem: 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": def __new__(cls, *_args, **_kwargs) -> "ModelItem":
from .model import Model from .model import Model
cls.parent_model: Optional[Model] = None cls.parent_model: Optional[Model] = None
@ -11,6 +24,8 @@ class ModelItem:
def __setattr__(self, name: str, value) -> None: def __setattr__(self, name: str, value) -> None:
"""If this item is in a `Model`, alert it of attribute changes."""
super().__setattr__(name, value) super().__setattr__(name, value)
if name != "parent_model" and self.parent_model is not None: if name != "parent_model" and self.parent_model is not None:
@ -24,6 +39,8 @@ class ModelItem:
@property @property
def serialized(self) -> Dict[str, Any]: def serialized(self) -> Dict[str, Any]:
"""Return this item as a dict ready to be passed to QML."""
return { return {
name: serialize_value_for_qml(getattr(self, name)) name: serialize_value_for_qml(getattr(self, name))
for name in dir(self) for name in dir(self)