Skip to content

Commit

Permalink
Write documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Dec 26, 2018
1 parent a3692dc commit f5c9ffd
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 70 deletions.
15 changes: 13 additions & 2 deletions src/aria2p/__init__.py
@@ -1,7 +1,18 @@
"""
Aria2p package.
This package provides a command-line tool and a Python library to interact with an `aria2c` daemon process through
JSON-RPC.
If you read this message, you probably want to learn about the library and not the command-line tool:
please refer to the README.md included in this package to get the link to the official documentation.
"""


from .api import API
from .client import JSONRPCClient, JSONRPCError
from .downloads import Download, Bittorrent, File
from .downloads import Download, BitTorrent, File
from .options import Options
from .stats import Stats

__all__ = ["API", "JSONRPCError", "JSONRPCClient", "Download", "Bittorrent", "File", "Options", "Stats"]
__all__ = ["API", "JSONRPCError", "JSONRPCClient", "Download", "BitTorrent", "File", "Options", "Stats"]
2 changes: 1 addition & 1 deletion src/aria2p/__main__.py
@@ -1,5 +1,5 @@
"""
Entrypoint module, in case you use `python -maria2p`.
Entry-point module, in case you use `python -maria2p`.
Why does this file exist, and why __main__? For more info, read:
Expand Down
18 changes: 17 additions & 1 deletion src/aria2p/api.py
@@ -1,9 +1,25 @@
"""
This module defines the API class, which makes use of a JSON-RPC client to provide higher-level methods to
interact easily with a remote aria2c process.
"""


from .downloads import Download
from .options import Options
from .stats import Stats


class API:
"""
A class providing high-level methods to interact with a remote aria2c process.
This class is instantiated with a reference to a :class:`client.JSONRPCClient` instance. It then uses this client
to call remote procedures, or remote methods. The client methods reflect exactly what aria2c is providing
through JSON-RPC, while this class's methods allow for easier / faster control of the remote process. It also
wraps the information the client retrieves in Python object, like :class:`downloads.Download`, allowing for
even more Pythonic interactions, without worrying about payloads, responses, JSON, etc..
"""

def __init__(self, json_rpc_client):
self.client = json_rpc_client
self.downloads = {}
Expand All @@ -22,7 +38,7 @@ def fetch_options(self):
self.options = Options(self, self.client.get_global_option)

def fetch_stats(self):
self.stats = Stats(self, self.client.get_global_stat())
self.stats = Stats(self.client.get_global_stat())

def add_magnet(self, magnet_uri):
pass
Expand Down
3 changes: 3 additions & 0 deletions src/aria2p/cli.py
Expand Up @@ -25,6 +25,7 @@


def get_method(name, default=None):
"""Return the actual method name from a differently formatted name."""
methods = {}
for method in JSONRPCClient.METHODS:
methods[method.lower()] = method
Expand All @@ -36,6 +37,7 @@ def get_method(name, default=None):


def get_parser():
"""Return a parser for the command-line options and arguments."""
parser = argparse.ArgumentParser()

mutually_exclusive = parser.add_mutually_exclusive_group()
Expand All @@ -55,6 +57,7 @@ def get_parser():


def main(args=None):
"""The main function, which is executed when you type ``aria2p`` or ``python -m aria2p``."""
client = JSONRPCClient()

parser = get_parser()
Expand Down
143 changes: 142 additions & 1 deletion src/aria2p/client.py
@@ -1,3 +1,9 @@
"""
This module defines the JSONRPCError and JSONRPCClient classes, which are used to communicate with a remote aria2c
process through the JSON-RPC protocol.
"""


import json
import requests

Expand All @@ -22,6 +28,8 @@


class JSONRPCError(Exception):
"""An exception specific to JSON-RPC errors."""

def __init__(self, code, message):
if code in JSONRPC_CODES:
message = f"{JSONRPC_CODES[code]}\n{message}"
Expand All @@ -31,6 +39,31 @@ def __init__(self, code, message):


class JSONRPCClient:
"""
The JSON-RPC client class.
In this documentation, all the following terms refer to the same entity, the remote aria2c process:
remote process, remote server, server, daemon process, background process, remote.
This class implements method to communicate with a daemon aria2c process through the JSON-RPC protocol.
Each method offered by the aria2c process is implemented in this class, in snake_case instead of camelCase
(example: add_uri instead of addUri).
The class defines a ``METHODS`` variable which contains the names of the available methods.
The class is instantiated using an address and port, and optionally a secret token. The token is never passed
as a method argument.
The class provides utility methods:
- call, which performs a JSON-RPC call for a single method;
- batch_call, which performs a JSON-RPC call for a list of methods;
- multicall2, which is an equivalent of multicall, but easier to use;
- post, which is responsible for actually sending a payload to the remote process using a POST request;
- get_payload, which is used to build payloads;
- get_params, which is used to build list of parameters.
"""

ADD_URI = "aria2.addUri"
ADD_TORRENT = "aria2.addTorrent"
ADD_METALINK = "aria2.addMetalink"
Expand Down Expand Up @@ -107,7 +140,15 @@ class JSONRPCClient:
LIST_NOTIFICATIONS,
]

def __init__(self, host="http://localhost", port=6800, secret=None):
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, secret=""):
"""
Initialization method.
Args:
host (str): the remote process address.
port (int): the remote process port.
secret (str): the secret token.
"""
host = host.rstrip("/")

self.host = host
Expand All @@ -119,10 +160,23 @@ def __str__(self):

@property
def server(self):
"""Property to return the full remote process / server address."""
return f"{self.host}:{self.port}/jsonrpc"

# utils
def call(self, method, params=None, msg_id=None, insert_secret=True):
"""
Call a single JSON-RPC method.
Args:
method (str): the method name. You can use the constant defined in :class:`client.JSONRPCClient`.
params (list of str): a list of parameters, as strings.
msg_id (int/str): the ID of the call, sent back with the server's answer.
insert_secret (bool): whether to insert the secret token in the parameters or not.
Returns:
The answer from the server, as a Python object (dict / list / str / int).
"""
params = self.get_params(*(params or []))

if insert_secret and self.secret:
Expand All @@ -135,6 +189,24 @@ def call(self, method, params=None, msg_id=None, insert_secret=True):
return self.post(self.get_payload(method, params, msg_id=msg_id))

def batch_call(self, calls, insert_secret=True):
"""
Call multiple methods in one request.
A batch call is simply a list of full payloads, sent at once to the remote process. The differences with a
multicall are:
- multicall is defined in the JSON-RPC protocol specification, whereas batch_call is not
- multicall is a special "system" method, whereas batch_call is simply the concatenation of several methods
- multicall payloads define the "jsonrpc" and "id" keys only once, whereas these keys are repeated in
each part of the batch_call method
Args:
calls (list): a list of tuples composed of method name, parameters and ID.
insert_secret (bool): whether to insert the secret token in the parameters or not.
Returns:
The answer from the server, as a Python object (dict / list / str / int).
"""
payloads = []

for method, params, msg_id in calls:
Expand All @@ -148,6 +220,37 @@ def batch_call(self, calls, insert_secret=True):
return self.post(payload)

def multicall2(self, calls, insert_secret=True):
"""
An method equivalent to multicall, but with a simplified usage.
Instead of providing dictionaries with "methodName" and "params" keys and values, this method allows you
to provide the values only, in tuples of length 2.
With a classic multicall, you would write your params like:
[
{"methodName": client.REMOVE, "params": ["2089b05ecca3d829"]},
{"methodName": client.REMOVE, "params": ["2fa07b6e85c40205"]},
]
With multicall2, you can reduce the verbosity:
[
(client.REMOVE, ["2089b05ecca3d829"]),
(client.REMOVE, ["2fa07b6e85c40205"]),
]
Note:
multicall2 is not part of the JSON-RPC protocol specification.
It is implemented here as a simple convenience method.
Args:
calls (list): list of tuples composed of method name and parameters.
insert_secret (bool): whether to insert the secret token in the parameters or not.
Returns:
The answer from the server, as a Python object (dict / list / str / int).
"""
multicall_params = []

for method, params in calls:
Expand All @@ -161,13 +264,41 @@ def multicall2(self, calls, insert_secret=True):
return self.post(payload)

def post(self, payload):
"""
Send a POST request to the server.
The response is a JSON string, which we then load as a Python object.
Args:
payload (dict): the payload / data to send to the remote process. It contains the following key-value pairs:
"jsonrpc": "2.0", "method": method, "id": id, "params": params (optional).
Returns:
The answer from the server, as a Python object (dict / list / str / int).
Raises:
JSONRPCError: when the server returns an error (client/server error).
See the :class:`client.JSONRPCError` class.
"""
response = requests.post(self.server, data=payload).json()
if "result" in response:
return response["result"]
raise JSONRPCError(response["error"]["code"], response["error"]["message"])

@staticmethod
def get_payload(method, params=None, msg_id=None, as_json=True):
"""
Build a payload.
Args:
method (str): the method name. You can use the constant defined in :class:`client.JSONRPCClient`.
params (list): the list of parameters.
msg_id (int/str): the ID of the call, sent back with the server's answer.
as_json (bool): whether to return the payload as a JSON-string or Python dictionary.
Returns:
"""
payload = {"jsonrpc": "2.0", "method": method}

if msg_id is not None:
Expand All @@ -182,6 +313,16 @@ def get_payload(method, params=None, msg_id=None, as_json=True):

@staticmethod
def get_params(*args):
"""
Build the list of parameters.
This method simply removes the ``None`̀` values from the given arguments.
Args:
*args: list of parameters.
Returns:
A new list, with ``None``s filtered out.
"""
return [p for p in args if p is not None]

# aria2
Expand Down

0 comments on commit f5c9ffd

Please sign in to comment.