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 - 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

View File

@ -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.
""" """

View File

@ -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.
""" """

View File

@ -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:

View File

@ -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:

View File

@ -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":

View File

@ -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)

View File

@ -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()

View File

@ -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)):