Update docstrings
This commit is contained in:
parent
04790b3ed3
commit
77d877047b
1
TODO.md
1
TODO.md
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
- Catch server 5xx errors when sending message and retry
|
- Catch server 5xx errors when sending message and retry
|
||||||
- nio ClientTimeout (to fix sync hanging after network changes or hibernation)
|
- nio ClientTimeout (to fix sync hanging after network changes or hibernation)
|
||||||
- Update docstrings
|
|
||||||
- Update README.md
|
- Update README.md
|
||||||
|
|
||||||
## Refactoring
|
## Refactoring
|
||||||
|
|
|
@ -29,43 +29,42 @@ class Backend:
|
||||||
"""Manage matrix clients and provide other useful general methods.
|
"""Manage matrix clients and provide other useful general methods.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
saved_accounts: User config file for saved matrix account details.
|
saved_accounts: User config file for saved matrix account.
|
||||||
|
|
||||||
ui_settings: User config file for QML interface settings.
|
ui_settings: User config file for QML interface settings.
|
||||||
|
|
||||||
ui_state: User data file for saving/restoring QML UI state.
|
ui_state: User data file for saving/restoring QML UI state.
|
||||||
|
|
||||||
history: User data file for saving/restoring lines typed into QML
|
history: User data file for saving/restoring text typed into QML
|
||||||
components.
|
components.
|
||||||
|
|
||||||
models: A dict containing our data models that are synchronized between
|
models: A mapping containing our data models that are
|
||||||
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.
|
||||||
|
|
||||||
The dict keys are the `Model`'s synchronization ID,
|
If a non-existent key is accessed, it is creating with an
|
||||||
which are in one of these form:
|
associated `Model` and returned.
|
||||||
|
|
||||||
- A `ModelItem` type, the type of item that this model stores;
|
The mapping keys are the `Model`'s synchronization ID,
|
||||||
- A `(ModelItem, str...)` tuple.
|
which a strings or tuple of strings.
|
||||||
|
|
||||||
Currently used sync ID throughout the code are:
|
Currently used sync ID throughout the code are:
|
||||||
|
|
||||||
- `Account`: logged-in accounts;
|
- `"accounts"`: logged-in accounts;
|
||||||
|
|
||||||
- `(Room, "<user_id>")`: rooms a `user_id` account is part of;
|
- `("<user_id>", "rooms")`: rooms our account `user_id` is part of;
|
||||||
|
|
||||||
- `(Upload, "<user_id>")`: ongoing or failed file uploads for a
|
- `("<user_id>", "uploads")`: ongoing or failed file uploads for
|
||||||
`user_id` account;
|
our account `user_id`;
|
||||||
|
|
||||||
- `(Member, "<user_id>", "<room_id>")`: members in the room
|
- `("<user_id>", "<room_id>", "members")`: members in the room
|
||||||
`room_id` that account `user_id` is part of;
|
`room_id` that our account `user_id` is part of;
|
||||||
|
|
||||||
- `(Event, "<user_id>", "<room_id>")`: state events and messages
|
- `("<user_id>", "<room_id>", "events")`: state events and messages
|
||||||
in the room `room_id` that account `user_id` is part of.
|
in the room `room_id` that our account `user_id` is part of.
|
||||||
|
|
||||||
clients: A dict containing the logged `MatrixClient` objects we manage.
|
clients: A `{user_id: MatrixClient}` dict for the logged-in clients
|
||||||
Each client represents a logged-in account,
|
we managed. Every client is logged to one matrix account.
|
||||||
identified by its `user_id`.
|
|
||||||
|
|
||||||
media_cache: A matrix media cache for downloaded files.
|
media_cache: A matrix media cache for downloaded files.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -357,7 +357,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
async def _send_file(
|
async def _send_file(
|
||||||
self, item_uuid: UUID, room_id: str, path: Union[Path, str],
|
self, item_uuid: UUID, room_id: str, path: Union[Path, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Monitorably upload a file + thumbnail and send the built event."""
|
"""Upload and monitor a file + thumbnail and send the built event."""
|
||||||
|
|
||||||
# TODO: this function is way too complex, and most of it should be
|
# TODO: this function is way too complex, and most of it should be
|
||||||
# refactored into nio.
|
# refactored into nio.
|
||||||
|
@ -993,7 +993,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
|
|
||||||
|
|
||||||
async def clear_events(self, room_id: str) -> None:
|
async def clear_events(self, room_id: str) -> None:
|
||||||
"""Remove every `Event` of a room we registred in our model.
|
"""Remove every `Event` of a room we registered in our model.
|
||||||
|
|
||||||
The events will be gone from the UI, until the client is restarted.
|
The events will be gone from the UI, until the client is restarted.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -94,7 +94,15 @@ class Media:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def local_path(self) -> Path:
|
def local_path(self) -> Path:
|
||||||
"""The path where the file either exists or should be downloaded."""
|
"""The path where the file either exists or should be downloaded.
|
||||||
|
|
||||||
|
The returned paths are in this form:
|
||||||
|
```
|
||||||
|
<base download folder>/<homeserver domain>/
|
||||||
|
<file title>_<mxc id>.<file extension>`
|
||||||
|
```
|
||||||
|
e.g. `~/.cache/mirage/downloads/matrix.org/foo_Hm24ar11i768b0el.png`.
|
||||||
|
"""
|
||||||
|
|
||||||
parsed = urlparse(self.mxc)
|
parsed = urlparse(self.mxc)
|
||||||
mxc_id = parsed.path.lstrip("/")
|
mxc_id = parsed.path.lstrip("/")
|
||||||
|
@ -255,8 +263,12 @@ class Thumbnail(Media):
|
||||||
"""The path where the thumbnail either exists or should be downloaded.
|
"""The path where the thumbnail either exists or should be downloaded.
|
||||||
|
|
||||||
The returned paths are in this form:
|
The returned paths are in this form:
|
||||||
`<base thumbnail folder>/<homeserver domain>/<standard size>/<mxc id>`,
|
```
|
||||||
e.g. `~/.cache/appname/thumbnails/matrix.org/32x32/Hm24ar11i768b0el`.
|
<base thumbnail folder>/<homeserver domain>/<standard size>/
|
||||||
|
<file title>_<mxc id>.<file extension>`
|
||||||
|
```
|
||||||
|
e.g.
|
||||||
|
`~/.cache/mirage/thumbnails/matrix.org/32x32/foo_Hm24ar11i768b0el.png`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
size = self.normalize_size(self.server_size or self.wanted_size)
|
size = self.normalize_size(self.server_size or self.wanted_size)
|
||||||
|
@ -271,12 +283,16 @@ class Thumbnail(Media):
|
||||||
|
|
||||||
|
|
||||||
async def _get_local_existing_file(self) -> Path:
|
async def _get_local_existing_file(self) -> Path:
|
||||||
|
"""Return an existing thumbnail path or raise `FileNotFoundError`.
|
||||||
|
|
||||||
|
If we have a bigger size thumbnail downloaded than the `wanted_size`
|
||||||
|
for the media, return it instead of asking the server for a
|
||||||
|
smaller thumbnail.
|
||||||
|
"""
|
||||||
|
|
||||||
if self.local_path.exists():
|
if self.local_path.exists():
|
||||||
return self.local_path
|
return self.local_path
|
||||||
|
|
||||||
# If we have a bigger size thumbnail than the wanted_size for this pic,
|
|
||||||
# return it instead of asking the server for a smaller thumbnail.
|
|
||||||
|
|
||||||
try_sizes = ((32, 32), (96, 96), (320, 240), (640, 480), (800, 600))
|
try_sizes = ((32, 32), (96, 96), (320, 240), (640, 480), (800, 600))
|
||||||
parts = list(self.local_path.parts)
|
parts = list(self.local_path.parts)
|
||||||
size = self.normalize_size(self.server_size or self.wanted_size)
|
size = self.normalize_size(self.server_size or self.wanted_size)
|
||||||
|
@ -295,6 +311,8 @@ class Thumbnail(Media):
|
||||||
|
|
||||||
|
|
||||||
async def _get_remote_data(self) -> bytes:
|
async def _get_remote_data(self) -> bytes:
|
||||||
|
"""Return the (decrypted) media file's content from the server."""
|
||||||
|
|
||||||
parsed = urlparse(self.mxc)
|
parsed = urlparse(self.mxc)
|
||||||
|
|
||||||
if self.crypt_dict:
|
if self.crypt_dict:
|
||||||
|
|
|
@ -16,22 +16,17 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Model(MutableMapping):
|
class Model(MutableMapping):
|
||||||
"""A mapping of `{identifier: ModelItem}` synced between Python & QML.
|
"""A mapping of `{ModelItem.id: ModelItem}` synced between Python & QML.
|
||||||
|
|
||||||
From the Python side, the model is usable like a normal dict of
|
From the Python side, the model is usable like a normal dict of
|
||||||
`ModelItem` subclass objects.
|
`ModelItem` subclass objects.
|
||||||
Different types of `ModelItem` must not be mixed in the same model.
|
Different types of `ModelItem` must not be mixed in the same model.
|
||||||
|
|
||||||
When items are added, changed or removed from the model, a synchronization
|
When items are added, replaced, removed, have field value changes, or the
|
||||||
with QML is scheduled.
|
model is cleared, corresponding `PyOtherSideEvent` are fired to inform
|
||||||
The model will synchronize with QML no more than every 0.25s, for
|
QML of the changes so that it can keep its models in sync.
|
||||||
performance reasons; though it is possible to request an instant sync
|
|
||||||
via `sync_now()` for certain cases when this delay is unacceptable.
|
|
||||||
|
|
||||||
Model data is sent to QML using a `ModelUpdated` event from the
|
Items in the model are kept sorted using the `ModelItem` subclass `__lt__`.
|
||||||
`pyotherside_events` module.
|
|
||||||
The data is a list of serialized `ModelItem` dicts, as expected
|
|
||||||
by QML for components like `ListView`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sync_id: SyncId) -> None:
|
def __init__(self, sync_id: SyncId) -> None:
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ModelItem:
|
||||||
|
|
||||||
Subclasses are also expected to implement `__lt__()`,
|
Subclasses are also expected to implement `__lt__()`,
|
||||||
to provide support for comparisons with the `<`, `>`, `<=`, `=>` operators
|
to provide support for comparisons with the `<`, `>`, `<=`, `=>` operators
|
||||||
and thus allow a `Model` to sort its `ModelItem`s.
|
and thus allow a `Model` to keep its data sorted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, *_args, **_kwargs) -> "ModelItem":
|
def __new__(cls, *_args, **_kwargs) -> "ModelItem":
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ModelStore(UserDict):
|
||||||
|
|
||||||
The dict keys must be the sync ID of `Model` values.
|
The dict keys must be the sync ID of `Model` values.
|
||||||
If a non-existent key is accessed, a corresponding `Model` will be
|
If a non-existent key is accessed, a corresponding `Model` will be
|
||||||
created, put into the interal `data` dict and returned.
|
created, put into the internal `data` dict and returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data: Dict[SyncId, Model] = field(default_factory=dict)
|
data: Dict[SyncId, Model] = field(default_factory=dict)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class CoroutineDone(PyOtherSideEvent):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LoopException(PyOtherSideEvent):
|
class LoopException(PyOtherSideEvent):
|
||||||
"""Indicate that an uncaught exception occured in the asyncio loop."""
|
"""Indicate an uncaught exception occurance in the asyncio loop."""
|
||||||
|
|
||||||
message: str = field()
|
message: str = field()
|
||||||
exception: Optional[Exception] = field()
|
exception: Optional[Exception] = field()
|
||||||
|
@ -64,6 +64,8 @@ class LoopException(PyOtherSideEvent):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ModelItemInserted(PyOtherSideEvent):
|
class ModelItemInserted(PyOtherSideEvent):
|
||||||
|
"""Indicate a `ModelItem` insertion into a `Backend` `Model`."""
|
||||||
|
|
||||||
sync_id: SyncId = field()
|
sync_id: SyncId = field()
|
||||||
index: int = field()
|
index: int = field()
|
||||||
item: "ModelItem" = field()
|
item: "ModelItem" = field()
|
||||||
|
@ -71,6 +73,8 @@ class ModelItemInserted(PyOtherSideEvent):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ModelItemFieldChanged(PyOtherSideEvent):
|
class ModelItemFieldChanged(PyOtherSideEvent):
|
||||||
|
"""Indicate a `ModelItem`'s field value change in a `Backend` `Model`."""
|
||||||
|
|
||||||
sync_id: SyncId = field()
|
sync_id: SyncId = field()
|
||||||
item_index_then: int = field()
|
item_index_then: int = field()
|
||||||
item_index_now: int = field()
|
item_index_now: int = field()
|
||||||
|
@ -80,10 +84,14 @@ class ModelItemFieldChanged(PyOtherSideEvent):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ModelItemDeleted(PyOtherSideEvent):
|
class ModelItemDeleted(PyOtherSideEvent):
|
||||||
|
"""Indicate the removal of a `ModelItem` from a `Backend` `Model`."""
|
||||||
|
|
||||||
sync_id: SyncId = field()
|
sync_id: SyncId = field()
|
||||||
index: int = field()
|
index: int = field()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ModelCleared(PyOtherSideEvent):
|
class ModelCleared(PyOtherSideEvent):
|
||||||
|
"""Indicate that a `Backend` `Model` was cleared."""
|
||||||
|
|
||||||
sync_id: SyncId = field()
|
sync_id: SyncId = field()
|
||||||
|
|
|
@ -26,7 +26,7 @@ auto = autostr
|
||||||
|
|
||||||
|
|
||||||
class AutoStrEnum(Enum):
|
class AutoStrEnum(Enum):
|
||||||
"""An Enum where auto() assigns the member's name instead of an int.
|
"""An Enum where auto() assigns the member's name instead of an integer.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> class Fruits(AutoStrEnum): apple = auto()
|
>>> class Fruits(AutoStrEnum): apple = auto()
|
||||||
|
@ -130,12 +130,24 @@ def serialize_value_for_qml(value: Any, json_list_dicts: bool = False) -> Any:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
- Return the member's actual value for `Enum` members
|
- For `int`, `float`, `bool`, `str` and `datetime`: the unchanged value
|
||||||
- A `file://...` string for `Path` objects
|
|
||||||
- Strings for `UUID` objects
|
- For `Sequence` and `Mapping` subclasses (includes `list` and `dict`):
|
||||||
- A number of milliseconds for `datetime.timedelta` objects
|
a JSON dump if `json_list_dicts` is `True`, else the unchanged value
|
||||||
- The class `__name__` for class types.
|
|
||||||
- `ModelItem.serialized` for `ModelItem`s
|
- If the value as a `serialized` attribute or property, return that
|
||||||
|
|
||||||
|
- For `Enum` members, the actual value of the member
|
||||||
|
|
||||||
|
- For `Path` objects, a `file://<path...>` string
|
||||||
|
|
||||||
|
- For `UUID` object: the UUID in string form
|
||||||
|
|
||||||
|
- For `timedelta` objects: the delta as a number of milliseconds `int`
|
||||||
|
|
||||||
|
- For class types: the class `__name__`
|
||||||
|
|
||||||
|
- For anything else: the unchanged value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(value, (int, float, bool, str, datetime)):
|
if isinstance(value, (int, float, bool, str, datetime)):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user