From e851a46c77350ddce4d21d1112c931438579f131 Mon Sep 17 00:00:00 2001
From: Nico Alt <nicoalt@posteo.org>
Date: Thu, 3 Sep 2020 14:00:00 +0000
Subject: [PATCH] Add docstrings to everything

---
 briar_wrapper/__init__.py                     | 10 +++-
 briar_wrapper/api.py                          | 52 +++++++++++++++++++
 briar_wrapper/api_thread.py                   | 27 ++++++++++
 briar_wrapper/constants.py                    | 17 ++++++
 briar_wrapper/model.py                        |  6 +++
 briar_wrapper/models/__init__.py              |  6 +++
 briar_wrapper/models/contacts.py              | 50 +++++++++++++++---
 briar_wrapper/models/private_chat.py          | 28 ++++++++--
 briar_wrapper/models/socket_listener.py       | 31 +++++++++--
 setup.cfg                                     |  2 +
 tests/briar_wrapper/models/test_contacts.py   |  2 +-
 .../models/test_socket_listener.py            | 36 ++++++-------
 tests/conftest.py                             |  2 +-
 13 files changed, 236 insertions(+), 33 deletions(-)
 create mode 100644 briar_wrapper/models/__init__.py
 create mode 100644 setup.cfg

diff --git a/briar_wrapper/__init__.py b/briar_wrapper/__init__.py
index b72a85a..356d837 100644
--- a/briar_wrapper/__init__.py
+++ b/briar_wrapper/__init__.py
@@ -2,6 +2,14 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
 
-"""Wrapper for the Briar Headless REST API"""
+"""
+Wrapper for the Briar Headless REST API
+
+Before using `briar_wrapper` you need to initialize an instance of
+`briar_wrapper.api.Api`. You can check with `briar_wrapper.api.Api.has_account`
+whether you want want to `briar_wrapper.api.Api.login` or
+`briar_wrapper.api.Api.register`. Now you can start to use the wrappers in
+ `briar_wrapper.models`.
+"""
 
 __version__ = "0.0.3"
diff --git a/briar_wrapper/api.py b/briar_wrapper/api.py
index 9ab7457..d20de97 100644
--- a/briar_wrapper/api.py
+++ b/briar_wrapper/api.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Central API wrapper handling login and registration
+"""
 
 import os
 from threading import Thread
@@ -12,28 +15,66 @@ from briar_wrapper.models.socket_listener import SocketListener
 
 class Api:
 
+    # pylint: disable=line-too-long
     auth_token = None
+    """
+    Briar's authentication token
+
+    [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#how-to-use)
+    """
+
     socket_listener = None
+    """
+    `briar_wrapper.api.Api`'s instance of
+    `briar_wrapper.models.socket_listener.SocketListener`
+    """
 
     _api_thread = None
 
     def __init__(self, headless_jar):
+        """
+        Initialize with path to Briar Headless JAR `headless_jar`
+        """
         self._api_thread = ApiThread(self, headless_jar)
 
     @staticmethod
     def has_account():
+        """
+        Checks if `briar_wrapper.constants.BRIAR_DB` exists
+
+        .. versionadded:: 0.0.3
+        """
         return os.path.isfile(BRIAR_DB)
 
     def is_running(self):
+        """
+        Returns `True` if `briar_wrapper.api_thread.ApiThread` is running
+
+        .. versionadded:: 0.0.3
+        """
         return self._api_thread.is_running()
 
     def login(self, password, callback):
+        """
+        Login to Briar API with `password`.
+
+        Calls `callback` once login process finished.
+
+        .. versionadded:: 0.0.3
+        """
         self._start_and_watch(callback)
         startup_thread = Thread(target=self._api_thread.login,
                                 args=(password,), daemon=True)
         startup_thread.start()
 
     def register(self, credentials, callback):
+        """
+        Register at Briar API with 2-tuple `credentials`.
+
+        Calls `callback` once registration process finished.
+
+        .. versionadded:: 0.0.3
+        """
         if len(credentials) != 2:
             raise Exception("Can't process credentials")
         self._start_and_watch(callback)
@@ -42,6 +83,11 @@ class Api:
         startup_thread.start()
 
     def stop(self):
+        """
+        Stops API wrapper
+
+        .. versionadded:: 0.0.3
+        """
         self._api_thread.stop()
 
     def _start_and_watch(self, callback):
@@ -49,6 +95,12 @@ class Api:
         self._api_thread.watch(callback)
 
     def on_successful_startup(self, callback):
+        """
+        Called by `briar_wrapper.api_thread.ApiThread` if startup finished
+        successfully.
+
+        Should not be called from outside `briar_wrapper`.
+        """
         self._load_auth_token()
         self.socket_listener = SocketListener(self)
         callback(True)
diff --git a/briar_wrapper/api_thread.py b/briar_wrapper/api_thread.py
index 18b5928..2a9b32d 100644
--- a/briar_wrapper/api_thread.py
+++ b/briar_wrapper/api_thread.py
@@ -1,6 +1,11 @@
 # Copyright (c) 2020 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Handles background thread for `briar_wrapper.api.Api`
+
+**Should not be called from outside `briar_wrapper`.**
+"""
 
 from subprocess import Popen, PIPE, STDOUT
 from threading import Thread
@@ -17,29 +22,48 @@ class ApiThread:
     _process = None
 
     def __init__(self, api, headless_jar):
+        """
+        Initialize with `briar_wrapper.api.Api` instance `api` and
+        path to Briar Headless JAR `headless_jar`
+        """
         self._api = api
         self._command = ["java", "-jar", headless_jar]
 
     def is_running(self):
+        """
+        Returns `True` if background thread is runnning
+        """
         return (self._process is not None) and (self._process.poll() is None)
 
     def start(self):
+        """
+        Starts background thread
+        """
         if self.is_running():
             raise Exception("API already running")
         self._process = Popen(self._command, stdin=PIPE,
                               stdout=PIPE, stderr=STDOUT)
 
     def watch(self, callback):
+        """
+        Watches startup of background thread and calls `callback` once finished
+        """
         watch_thread = Thread(target=self._watch_thread, args=(callback,),
                               daemon=True)
         watch_thread.start()
 
     def login(self, password):
+        """
+        Actually logins to Briar API with `password`
+        """
         if not self.is_running():
             raise Exception("Can't login; API not running")
         self._process.communicate((f"{password}\n").encode("utf-8"))
 
     def register(self, credentials):
+        """
+        Actually registers new account at Briar API with 2-tuple `credentials`
+        """
         if not self.is_running():
             raise Exception("Can't register; API not running")
         self._process.communicate((credentials[0] + '\n' +
@@ -47,6 +71,9 @@ class ApiThread:
                                    credentials[1] + '\n').encode("utf-8"))
 
     def stop(self):
+        """
+        Stops background thread
+        """
         if not self.is_running():
             raise Exception("Nothing to stop")
         self._process.terminate()
diff --git a/briar_wrapper/constants.py b/briar_wrapper/constants.py
index 34dd83f..06ac4f0 100644
--- a/briar_wrapper/constants.py
+++ b/briar_wrapper/constants.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Constants used in `briar_wrapper`
+"""
 
 from os.path import join
 from pathlib import Path
@@ -14,7 +17,21 @@ _HOST = "%s://localhost:7000"
 _VERSION_SUFFIX = "v1/"
 
 BRIAR_AUTH_TOKEN = join(_BRIAR_DIR, "auth_token")
+"""
+Path to Briar's authentication token
+"""
+
 BRIAR_DB = join(_BRIAR_DIR, "db", "db.mv.db")
+"""
+Path to Briar's database
+"""
 
 BASE_HTTP_URL = urljoin(_HOST % "http", _VERSION_SUFFIX)
+"""
+Base URL to construct resource's URLs
+"""
+
 WEBSOCKET_URL = urljoin(_HOST % "ws", "%s/ws" % _VERSION_SUFFIX)
+"""
+Websocket URL used in `briar_wrapper.models.socket_listener.SocketListener`
+"""
diff --git a/briar_wrapper/model.py b/briar_wrapper/model.py
index f432c2b..cd53777 100644
--- a/briar_wrapper/model.py
+++ b/briar_wrapper/model.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Abstract base class for all `briar_wrapper.models`
+"""
 
 
 class Model:  # pylint: disable=too-few-public-methods
@@ -8,6 +11,9 @@ class Model:  # pylint: disable=too-few-public-methods
     _headers = {}
 
     def __init__(self, api):
+        """
+        Initialize with `briar_wrapper.api.Api` instance `api`
+        """
         self._api = api
         self._initialize_headers()
 
diff --git a/briar_wrapper/models/__init__.py b/briar_wrapper/models/__init__.py
new file mode 100644
index 0000000..c7175c7
--- /dev/null
+++ b/briar_wrapper/models/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) 2020 Nico Alt
+# SPDX-License-Identifier: AGPL-3.0-only
+# License-Filename: LICENSE.md
+"""
+Contains wrappers around different resources
+"""
diff --git a/briar_wrapper/models/contacts.py b/briar_wrapper/models/contacts.py
index 81d6304..dc88afb 100644
--- a/briar_wrapper/models/contacts.py
+++ b/briar_wrapper/models/contacts.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Wrapper around Briar API's _/contacts/_ resource
+"""
 
 from operator import itemgetter
 from urllib.parse import urljoin
@@ -14,32 +17,66 @@ from briar_wrapper.model import Model
 
 class Contacts(Model):
 
-    API_ENDPOINT = "contacts/"
-    CONNECTION_EVENTS = ("ContactConnectedEvent", "ContactDisconnectedEvent")
+    _API_ENDPOINT = "contacts/"
+    _CONNECTION_EVENTS = ("ContactConnectedEvent", "ContactDisconnectedEvent")
 
     _connections_callback = None
 
     def add_pending(self, link, alias):
-        url = urljoin(BASE_HTTP_URL, self.API_ENDPOINT + "add/pending/")
+        # pylint: disable=line-too-long
+        """
+
+        Adds pending contact to Briar with `link` URL and `alias`
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#adding-a-contact)
+
+        .. versionadded:: 0.0.3
+        """
+        url = urljoin(BASE_HTTP_URL, self._API_ENDPOINT + "add/pending/")
         _post(url, headers=self._headers, json={"link": link, "alias": alias})
 
     def get(self):
-        url = urljoin(BASE_HTTP_URL, self.API_ENDPOINT)
+        # pylint: disable=line-too-long
+        """
+        Returns sorted list containing all contacts
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#listing-all-contacts)
+
+        .. versionadded:: 0.0.3
+        .. versionchanged:: 0.0.4
+        """
+        url = urljoin(BASE_HTTP_URL, self._API_ENDPOINT)
         request = _get(url, headers=self._headers)
         contacts = request.json()
         contacts = Contacts._sort_contact_list(contacts)
         return contacts
 
     def get_link(self):
-        url = urljoin(BASE_HTTP_URL, self.API_ENDPOINT + "add/link/")
+        # pylint: disable=line-too-long
+        """
+        Returns _briar://_ link
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#adding-a-contact)
+
+        .. versionadded:: 0.0.3
+        """
+        url = urljoin(BASE_HTTP_URL, self._API_ENDPOINT + "add/link/")
         request = _get(url, headers=self._headers).json()
         return request['link']
 
     def watch_connections(self, callback):
+        # pylint: disable=line-too-long
+        """
+        Calls `callback` whenever a contact's connection status changes
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#a-contact-connected-or-disconnected)
+
+        .. versionadded:: 0.0.4
+        """
         self._connections_callback = callback
         signal_ids = list()
         event_callback = self._handle_connections_callback
-        for event in self.CONNECTION_EVENTS:
+        for event in self._CONNECTION_EVENTS:
             signal_id = self._api.socket_listener.connect(event,
                                                           event_callback)
             signal_ids.append(signal_id)
@@ -54,6 +91,7 @@ class Contacts(Model):
         else:
             raise Exception(f"Wrong event in callback: {message['name']}")
 
+    @staticmethod
     def _sort_contact_list(contacts):
         contacts.sort(key=itemgetter("lastChatActivity"),
                       reverse=True)
diff --git a/briar_wrapper/models/private_chat.py b/briar_wrapper/models/private_chat.py
index 129cc2f..d9bf4fe 100644
--- a/briar_wrapper/models/private_chat.py
+++ b/briar_wrapper/models/private_chat.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Wrapper around Briar API's _/messages/_ resource
+"""
 
 from urllib.parse import urljoin
 
@@ -12,19 +15,38 @@ from briar_wrapper.model import Model
 
 class PrivateChat(Model):
 
-    API_ENDPOINT = "messages/"
+    _API_ENDPOINT = "messages/"
 
     def __init__(self, api, contact_id):
+        """
+        Initialize with `briar_wrapper.api.Api` instance `api` and `contact_id`
+        """
         super().__init__(api)
         self._contact_id = contact_id
 
     def get(self):
+        # pylint: disable=line-too-long
+        """
+        Returns list containing all messages from contact
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#listing-all-private-messages)
+
+        .. versionadded:: 0.0.3
+        """
         url = urljoin(BASE_HTTP_URL,
-                      self.API_ENDPOINT + str(self._contact_id))
+                      self._API_ENDPOINT + str(self._contact_id))
         request = requests.get(url, headers=self._headers)
         return request.json()
 
     def send(self, message):
+        # pylint: disable=line-too-long
+        """
+        Sends `message` to contact
+
+        [Upstream documentation](https://code.briarproject.org/briar/briar/blob/master/briar-headless/README.md#writing-a-private-message)
+
+        .. versionadded:: 0.0.3
+        """
         url = urljoin(BASE_HTTP_URL,
-                      self.API_ENDPOINT + str(self._contact_id))
+                      self._API_ENDPOINT + str(self._contact_id))
         requests.post(url, headers=self._headers, json={"text": message})
diff --git a/briar_wrapper/models/socket_listener.py b/briar_wrapper/models/socket_listener.py
index 91274e5..3522ee4 100644
--- a/briar_wrapper/models/socket_listener.py
+++ b/briar_wrapper/models/socket_listener.py
@@ -1,6 +1,9 @@
 # Copyright (c) 2019 Nico Alt
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
+"""
+Wrapper around Briar API's websocket stream
+"""
 
 import asyncio
 import json
@@ -12,22 +15,44 @@ from briar_wrapper.constants import WEBSOCKET_URL
 from briar_wrapper.model import Model
 
 
-class SocketListener():  # pylint: disable=too-few-public-methods
+class SocketListener(Model):  # pylint: disable=too-few-public-methods
 
     def __init__(self, api):
-        self._api = api
+        super().__init__(api)
         self._signals = dict()
         self._signals_lock = Lock()
         self._highest_signal_id = -1
         self._start_websocket_thread()
 
     def connect(self, event, callback):
+        """
+        Connects to one of websocket API's `event`s. If the websocket API sends
+        out a message with given `event`, `callback` will be called.
+
+        Returns
+        -------
+        int
+            Signal ID used for
+            `briar_wrapper.models.socket_listener.SocketListener.disconnect`
+            later on
+
+        .. versionadded:: 0.0.3
+        """
         self._signals_lock.acquire()
         signal_id = self._add_signal(event, callback)
         self._signals_lock.release()
         return signal_id
 
     def disconnect(self, signal_id):
+        """
+        Disconnect signal with `signal_id` from
+        `briar_wrapper.models.socket_listener.SocketListener`. The `callback`
+        given at
+        `briar_wrapper.models.socket_listener.SocketListener.connect` will not
+        be called anymore.
+
+        .. versionadded:: 0.0.3
+        """
         self._signals_lock.acquire()
         self._remove_signal(signal_id)
         self._signals_lock.release()
@@ -38,7 +63,7 @@ class SocketListener():  # pylint: disable=too-few-public-methods
         self._signals[signal_id] = {
             "event": event,
             "callback": callback
-            }
+        }
         return signal_id
 
     def _remove_signal(self, signal_id):
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..42fc97e
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[pycodestyle]
+ignore = E501, W504
diff --git a/tests/briar_wrapper/models/test_contacts.py b/tests/briar_wrapper/models/test_contacts.py
index 3af6d2e..68029ab 100644
--- a/tests/briar_wrapper/models/test_contacts.py
+++ b/tests/briar_wrapper/models/test_contacts.py
@@ -101,7 +101,7 @@ def test_get_link(api, request_headers, requests_mock):
 
 def test_watch_signal_added(api, mocker):
     contacts = Contacts(api)
-    contacts._api.socket_listener = SocketListener(None)
+    contacts._api.socket_listener = SocketListener(api)
     contacts._api.socket_listener._highest_signal_id = 136
 
     assert contacts._api.socket_listener._signals == dict()
diff --git a/tests/briar_wrapper/models/test_socket_listener.py b/tests/briar_wrapper/models/test_socket_listener.py
index 24256ef..60e0a99 100644
--- a/tests/briar_wrapper/models/test_socket_listener.py
+++ b/tests/briar_wrapper/models/test_socket_listener.py
@@ -9,30 +9,30 @@ from briar_wrapper.models.socket_listener import SocketListener
 MODULE = "briar_wrapper.models.socket_listener.%s"
 
 
-def test_init_websocket_thread(mocker):
+def test_init_websocket_thread(api, mocker):
     thread_mock = mocker.patch(MODULE % "Thread")
 
-    SocketListener(None)
+    SocketListener(api)
 
     thread_mock.assert_called_once()
 
 
-def test_init_watch_loop(mocker):
+def test_init_watch_loop(api, mocker):
     watch_loop_mock = mocker.patch(MODULE % "SocketListener._start_watch_loop")
 
-    SocketListener(None)
+    SocketListener(api)
 
     watch_loop_mock.assert_called_once()
 
 
-def test_connect_lock(mocker):
+def test_connect_lock(api, mocker):
     lock_mock = mocker.Mock()
 
     manager = mocker.Mock()
     manager.attach_mock(lock_mock.acquire, 'acquire_mock')
     manager.attach_mock(lock_mock.release, 'release_mock')
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._signals_lock = lock_mock
 
     socket_listener.connect(None, None)
@@ -45,11 +45,11 @@ def test_connect_lock(mocker):
     assert manager.mock_calls == expected_calls
 
 
-def test_connect_signal_added(mocker):
+def test_connect_signal_added(api, mocker):
     event_mock = mocker.MagicMock()
     callback_mock = mocker.MagicMock()
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._highest_signal_id = 136
 
     assert socket_listener._signals == dict()
@@ -66,11 +66,11 @@ def test_connect_signal_added(mocker):
     assert socket_listener._signals == expected_signals
 
 
-def test_connect_signal_id(mocker):
+def test_connect_signal_id(api, mocker):
     event_mock = mocker.MagicMock()
     callback_mock = mocker.MagicMock()
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._highest_signal_id = 137
 
     signal_id = socket_listener.connect(event_mock, callback_mock)
@@ -78,14 +78,14 @@ def test_connect_signal_id(mocker):
     assert signal_id == 137 + 1
 
 
-def test_disconnect_lock(mocker):
+def test_disconnect_lock(api, mocker):
     lock_mock = mocker.Mock()
 
     manager = mocker.Mock()
     manager.attach_mock(lock_mock.acquire, 'acquire_mock')
     manager.attach_mock(lock_mock.release, 'release_mock')
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._signals_lock = lock_mock
     socket_listener._signals = mocker.MagicMock()
 
@@ -99,8 +99,8 @@ def test_disconnect_lock(mocker):
     assert manager.mock_calls == expected_calls
 
 
-def test_disconnect_signal_removed(mocker):
-    socket_listener = SocketListener(None)
+def test_disconnect_signal_removed(api, mocker):
+    socket_listener = SocketListener(api)
     socket_listener._signals = {
         137: {
             "event": None,
@@ -123,13 +123,13 @@ def test_watch_messages():
     pass
 
 
-def test_call_signal_callbacks(mocker):
+def test_call_signal_callbacks(api, mocker):
     message = {
         'name': 'VeryImportantEvent'
     }
     callback_mock = mocker.MagicMock()
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._signals = {
         137: {
             "event": 'VeryImportantEvent',
@@ -142,14 +142,14 @@ def test_call_signal_callbacks(mocker):
     callback_mock.assert_called_once_with(message)
 
 
-def test_call_signal_callbacks_lock(mocker):
+def test_call_signal_callbacks_lock(api, mocker):
     lock_mock = mocker.Mock()
 
     manager = mocker.Mock()
     manager.attach_mock(lock_mock.acquire, 'acquire_mock')
     manager.attach_mock(lock_mock.release, 'release_mock')
 
-    socket_listener = SocketListener(None)
+    socket_listener = SocketListener(api)
     socket_listener._signals_lock = lock_mock
     socket_listener._signals = mocker.MagicMock()
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 91a2eb2..9fa58b9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -23,5 +23,5 @@ def auth_token():
 def request_headers(auth_token):
     request_headers = {
         "Authorization": 'Bearer %s' % auth_token
-        }
+    }
     return request_headers
-- 
GitLab