Update docstrings

This commit is contained in:
miruka 2020-03-12 14:41:00 -04:00
parent 04790b3ed3
commit 77d877047b
9 changed files with 79 additions and 48 deletions

View File

@ -4,7 +4,6 @@
- Catch server 5xx errors when sending message and retry
- nio ClientTimeout (to fix sync hanging after network changes or hibernation)
- Update docstrings
- Update README.md
## Refactoring

View File

@ -29,43 +29,42 @@ class Backend:
"""Manage matrix clients and provide other useful general methods.
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_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.
models: A dict containing our data models that are synchronized between
the Python backend and the QML UI.
models: A mapping containing our data models that are
synchronized between the Python backend and the QML UI.
The models should only ever be modified from the backend.
The dict keys are the `Model`'s synchronization ID,
which are in one of these form:
If a non-existent key is accessed, it is creating with an
associated `Model` and returned.
- A `ModelItem` type, the type of item that this model stores;
- A `(ModelItem, str...)` tuple.
The mapping keys are the `Model`'s synchronization ID,
which a strings or tuple of strings.
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` account;
- `("<user_id>", "uploads")`: ongoing or failed file uploads for
our account `user_id`;
- `(Member, "<user_id>", "<room_id>")`: members in the room
`room_id` that account `user_id` is part of;
- `("<user_id>", "<room_id>", "members")`: members in the room
`room_id` that our account `user_id` is part of;
- `(Event, "<user_id>", "<room_id>")`: state events and messages
in the room `room_id` that account `user_id` is part of.
- `("<user_id>", "<room_id>", "events")`: state events and messages
in the room `room_id` that our account `user_id` is part of.
clients: A dict containing the logged `MatrixClient` objects we manage.
Each client represents a logged-in account,
identified by its `user_id`.
clients: A `{user_id: MatrixClient}` dict for the logged-in clients
we managed. Every client is logged to one matrix account.
media_cache: A matrix media cache for downloaded files.
"""

View File

@ -357,7 +357,7 @@ class MatrixClient(nio.AsyncClient):
async def _send_file(
self, item_uuid: UUID, room_id: str, path: Union[Path, str],
) -> 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
# refactored into nio.
@ -993,7 +993,7 @@ class MatrixClient(nio.AsyncClient):
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.
"""

View File

@ -94,7 +94,15 @@ class Media:
@property
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)
mxc_id = parsed.path.lstrip("/")
@ -255,8 +263,12 @@ class Thumbnail(Media):
"""The path where the thumbnail either exists or should be downloaded.
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)
@ -271,12 +283,16 @@ class Thumbnail(Media):
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():
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))
parts = list(self.local_path.parts)
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:
"""Return the (decrypted) media file's content from the server."""
parsed = urlparse(self.mxc)
if self.crypt_dict:

View File

@ -16,22 +16,17 @@ if TYPE_CHECKING:
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
`ModelItem` subclass objects.
Different types of `ModelItem` must not be mixed in the same model.
When items are added, changed or removed from the model, a synchronization
with QML is scheduled.
The model will synchronize with QML no more than every 0.25s, for
performance reasons; though it is possible to request an instant sync
via `sync_now()` for certain cases when this delay is unacceptable.
When items are added, replaced, removed, have field value changes, or the
model is cleared, corresponding `PyOtherSideEvent` are fired to inform
QML of the changes so that it can keep its models in sync.
Model data is sent to QML using a `ModelUpdated` event from the
`pyotherside_events` module.
The data is a list of serialized `ModelItem` dicts, as expected
by QML for components like `ListView`.
Items in the model are kept sorted using the `ModelItem` subclass `__lt__`.
"""
def __init__(self, sync_id: SyncId) -> None:

View File

@ -17,7 +17,7 @@ class ModelItem:
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.
and thus allow a `Model` to keep its data sorted.
"""
def __new__(cls, *_args, **_kwargs) -> "ModelItem":

View File

@ -14,7 +14,7 @@ class ModelStore(UserDict):
The dict keys must be the sync ID of `Model` values.
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)

View File

@ -55,7 +55,7 @@ class CoroutineDone(PyOtherSideEvent):
@dataclass
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()
exception: Optional[Exception] = field()
@ -64,6 +64,8 @@ class LoopException(PyOtherSideEvent):
@dataclass
class ModelItemInserted(PyOtherSideEvent):
"""Indicate a `ModelItem` insertion into a `Backend` `Model`."""
sync_id: SyncId = field()
index: int = field()
item: "ModelItem" = field()
@ -71,6 +73,8 @@ class ModelItemInserted(PyOtherSideEvent):
@dataclass
class ModelItemFieldChanged(PyOtherSideEvent):
"""Indicate a `ModelItem`'s field value change in a `Backend` `Model`."""
sync_id: SyncId = field()
item_index_then: int = field()
item_index_now: int = field()
@ -80,10 +84,14 @@ class ModelItemFieldChanged(PyOtherSideEvent):
@dataclass
class ModelItemDeleted(PyOtherSideEvent):
"""Indicate the removal of a `ModelItem` from a `Backend` `Model`."""
sync_id: SyncId = field()
index: int = field()
@dataclass
class ModelCleared(PyOtherSideEvent):
"""Indicate that a `Backend` `Model` was cleared."""
sync_id: SyncId = field()

View File

@ -26,7 +26,7 @@ auto = autostr
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:
>>> class Fruits(AutoStrEnum): apple = auto()
@ -130,12 +130,24 @@ def serialize_value_for_qml(value: Any, json_list_dicts: bool = False) -> Any:
Returns:
- Return the member's actual value for `Enum` members
- A `file://...` string for `Path` objects
- Strings for `UUID` objects
- A number of milliseconds for `datetime.timedelta` objects
- The class `__name__` for class types.
- `ModelItem.serialized` for `ModelItem`s
- For `int`, `float`, `bool`, `str` and `datetime`: the unchanged value
- For `Sequence` and `Mapping` subclasses (includes `list` and `dict`):
a JSON dump if `json_list_dicts` is `True`, else the unchanged value
- 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)):