update arg parsing to use docopt

This commit is contained in:
Jack Robison 2017-05-28 16:02:22 -04:00
parent 0eac1e6ed0
commit 57c3d2590c
5 changed files with 119 additions and 140 deletions

View file

@ -1,23 +1,68 @@
import argparse
import json import json
import os import os
import sys import sys
import colorama import colorama
from docopt import docopt
from collections import OrderedDict
from lbrynet import conf from lbrynet import conf
from lbrynet.core import utils from lbrynet.core import utils
from lbrynet.lbrynet_daemon.auth.client import JSONRPCException, LBRYAPIClient from lbrynet.lbrynet_daemon.auth.client import JSONRPCException, LBRYAPIClient
from lbrynet.lbrynet_daemon.Daemon import LOADING_WALLET_CODE from lbrynet.lbrynet_daemon.Daemon import LOADING_WALLET_CODE, Daemon
from jsonrpc.common import RPCError from jsonrpc.common import RPCError
from urllib2 import URLError, HTTPError from urllib2 import URLError, HTTPError
from httplib import UNAUTHORIZED from httplib import UNAUTHORIZED
def main(): def remove_brackets(key):
colorama.init() if key.startswith("<") and key.endswith(">"):
parser = argparse.ArgumentParser(add_help=False) return str(key[1:-1])
_, arguments = parser.parse_known_args() return key
def set_flag_vals(flag_names, parsed_args):
kwargs = OrderedDict()
for key, arg in parsed_args.iteritems():
if arg is None:
continue
elif key.startswith("--"):
if remove_brackets(key[2:]) not in kwargs:
k = remove_brackets(key[2:])
kwargs[k] = guess_type(arg)
elif key in flag_names:
if remove_brackets(flag_names[key]) not in kwargs:
kwargs[remove_brackets(flag_names[key])] = guess_type(arg)
elif remove_brackets(key) not in kwargs:
kwargs[remove_brackets(key)] = guess_type(arg)
return kwargs
def main():
if len(sys.argv[1:]):
method, args = sys.argv[1], sys.argv[2:]
else:
print_help()
return
if method in ['help', '--help', '-h']:
if len(args) == 1:
print_help_for_command(args[0])
else:
print_help()
return
if method not in Daemon.callable_methods:
print_error("\"%s\" is not a valid command." % method)
return
fn = Daemon.callable_methods[method]
if hasattr(fn, "_flags"):
flag_names = fn._flags
else:
flag_names = {}
parsed = docopt(fn.__doc__, args)
kwargs = set_flag_vals(flag_names, parsed)
colorama.init()
conf.initialize_settings() conf.initialize_settings()
api = LBRYAPIClient.get_client() api = LBRYAPIClient.get_client()
@ -44,94 +89,43 @@ def main():
print " Status: " + message print " Status: " + message
return 1 return 1
if len(arguments) < 1:
print_help(api)
return 1
method = arguments[0]
try:
params = parse_params(arguments[1:])
except InvalidParameters as e:
print_error(e.message)
return 1
# TODO: check if port is bound. Error if its not # TODO: check if port is bound. Error if its not
if method in ['--help', '-h', 'help']: try:
if len(params) == 0: result = api.call(method, **kwargs)
print_help(api) if isinstance(result, basestring):
elif 'command' not in params: # printing the undumped string is prettier
print_error( print result
'To get help on a specific command, use `{} help command=COMMAND_NAME`'.format(
os.path.basename(sys.argv[0]))
)
else: else:
print_help_for_command(api, params['command']) print utils.json_dumps_pretty(result)
except (RPCError, KeyError, JSONRPCException, HTTPError) as err:
error_data = None
if isinstance(err, HTTPError):
error_body = err.read()
try:
error_data = json.loads(error_body)
except ValueError:
print (
"There was an error, and the response was not valid JSON.\n" +
"Raw JSONRPC response:\n" + error_body
)
return 1
elif method not in api.commands(): print_error(error_data['error']['message'] + "\n", suggest_help=False)
print_error("'" + method + "' is not a valid command.") else:
print_error("Something went wrong\n", suggest_help=False)
else: print_help_for_command(method)
try:
result = api.call(method, params)
if isinstance(result, basestring):
# printing the undumped string is prettier
print result
else:
print utils.json_dumps_pretty(result)
except (RPCError, KeyError, JSONRPCException, HTTPError) as err:
error_data = None
if isinstance(err, HTTPError):
error_body = err.read()
try:
error_data = json.loads(error_body)
except ValueError:
print (
"There was an error, and the response was not valid JSON.\n" +
"Raw JSONRPC response:\n" + error_body
)
return 1
print_error(error_data['error']['message'] + "\n", suggest_help=False) if 'data' in error_data['error'] and 'traceback' in error_data['error']['data']:
else: print "Here's the traceback for the error you encountered:"
print_error("Something went wrong\n", suggest_help=False) print "\n".join(error_data['error']['data']['traceback'])
return 1
print_help_for_command(api, method)
if 'data' in error_data['error'] and 'traceback' in error_data['error']['data']:
print "Here's the traceback for the error you encountered:"
print "\n".join(error_data['error']['data']['traceback'])
return 1
def parse_params(params):
if len(params) > 1:
return get_params_from_kwargs(params)
elif len(params) == 1:
try:
return json.loads(params[0])
except ValueError:
return get_params_from_kwargs(params)
else:
return {}
class InvalidParameters(Exception):
pass
def get_params_from_kwargs(params):
params_for_return = {}
for i in params:
try:
eq_pos = i.index('=')
except ValueError:
raise InvalidParameters('{} is not in <key>=<value> format'.format(i))
k, v = i[:eq_pos], i[eq_pos + 1:]
params_for_return[k] = guess_type(v)
return params_for_return
def guess_type(x): def guess_type(x):
if not isinstance(x, (unicode, str)):
return x
if x in ('true', 'True', 'TRUE'): if x in ('true', 'True', 'TRUE'):
return True return True
if x in ('false', 'False', 'FALSE'): if x in ('false', 'False', 'FALSE'):
@ -159,7 +153,8 @@ def print_error(message, suggest_help=True):
print_help_suggestion() print_help_suggestion()
def print_help(api): def print_help():
commands = Daemon.callable_methods.keys()
print "\n".join([ print "\n".join([
"NAME", "NAME",
" lbrynet-cli - LBRY command line client.", " lbrynet-cli - LBRY command line client.",
@ -170,20 +165,18 @@ def print_help(api):
"EXAMPLES", "EXAMPLES",
" lbrynet-cli commands # list available commands", " lbrynet-cli commands # list available commands",
" lbrynet-cli status # get daemon status", " lbrynet-cli status # get daemon status",
" lbrynet-cli resolve_name name=what # resolve a name", " lbrynet-cli resolve_name what # resolve a name",
" lbrynet-cli help command=resolve_name # get help for a command", " lbrynet-cli help resolve_name # get help for a command",
"", "",
"COMMANDS", "COMMANDS",
wrap_list_to_term_width(api.commands(), prefix=' ') wrap_list_to_term_width(commands, prefix=' ')
]) ])
def print_help_for_command(api, command): def print_help_for_command(command):
help_response = api.call('help', {'command': command}) fn = Daemon.callable_methods.get(command)
print "Help for %s method:" % command if fn:
message = help_response['help'] if 'help' in help_response else help_response print "Help for %s method:\n%s" % (command, fn.__doc__)
message = "\n".join([' ' + line for line in message.split("\n")])
print message
def wrap_list_to_term_width(l, width=None, separator=', ', prefix=''): def wrap_list_to_term_width(l, width=None, separator=', ', prefix=''):

View file

@ -1,7 +1,7 @@
import logging import logging
import urlparse import urlparse
import inspect
import json import json
import inspect
from decimal import Decimal from decimal import Decimal
from zope.interface import implements from zope.interface import implements
@ -15,9 +15,9 @@ from traceback import format_exc
from lbrynet import conf from lbrynet import conf
from lbrynet.core.Error import InvalidAuthenticationToken from lbrynet.core.Error import InvalidAuthenticationToken
from lbrynet.core import utils from lbrynet.core import utils
from lbrynet.undecorated import undecorated
from lbrynet.lbrynet_daemon.auth.util import APIKey, get_auth_message from lbrynet.lbrynet_daemon.auth.util import APIKey, get_auth_message
from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET
from lbrynet.undecorated import undecorated
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -338,12 +338,16 @@ class AuthJSONRPCServer(AuthorizedBase):
if args == EMPTY_PARAMS or args == []: if args == EMPTY_PARAMS or args == []:
args_dict = {} args_dict = {}
_args, _kwargs = (), {}
elif isinstance(args, dict): elif isinstance(args, dict):
args_dict = args args_dict = args
elif len(args) == 1 and isinstance(args[0], dict): elif len(args) == 1 and isinstance(args[0], dict):
# TODO: this is for backwards compatibility. Remove this once API and UI are updated # TODO: this is for backwards compatibility. Remove this once API and UI are updated
# TODO: also delete EMPTY_PARAMS then # TODO: also delete EMPTY_PARAMS then
args_dict = args[0] args_dict = args[0]
_args, _kwargs = (), args
elif isinstance(args, list):
_args, _kwargs = args, {}
else: else:
# d = defer.maybeDeferred(function, *args) # if we want to support positional args too # d = defer.maybeDeferred(function, *args) # if we want to support positional args too
raise ValueError('Args must be a dict') raise ValueError('Args must be a dict')
@ -400,28 +404,6 @@ class AuthJSONRPCServer(AuthorizedBase):
(utils.now() - time_in).total_seconds())) (utils.now() - time_in).total_seconds()))
return server.NOT_DONE_YET return server.NOT_DONE_YET
@staticmethod
def _check_params(function, args_dict):
argspec = inspect.getargspec(undecorated(function))
num_optional_params = 0 if argspec.defaults is None else len(argspec.defaults)
missing_required_params = [
required_param
for required_param in argspec.args[1:-num_optional_params]
if required_param not in args_dict
]
if len(missing_required_params):
return 'Missing required parameters', missing_required_params
extraneous_params = [] if argspec.keywords is not None else [
extra_param
for extra_param in args_dict
if extra_param not in argspec.args[1:]
]
if len(extraneous_params):
return 'Extraneous parameters', extraneous_params
return None, None
def _register_user_session(self, session_id): def _register_user_session(self, session_id):
""" """
Add or update a HMAC secret for a session Add or update a HMAC secret for a session
@ -502,6 +484,28 @@ class AuthJSONRPCServer(AuthorizedBase):
self._verify_method_is_callable(function_path) self._verify_method_is_callable(function_path)
return self.callable_methods.get(function_path) return self.callable_methods.get(function_path)
@staticmethod
def _check_params(function, args_dict):
argspec = inspect.getargspec(undecorated(function))
num_optional_params = 0 if argspec.defaults is None else len(argspec.defaults)
missing_required_params = [
required_param
for required_param in argspec.args[1:-num_optional_params]
if required_param not in args_dict
]
if len(missing_required_params):
return 'Missing required parameters', missing_required_params
extraneous_params = [] if argspec.keywords is not None else [
extra_param
for extra_param in args_dict
if extra_param not in argspec.args[1:]
]
if len(extraneous_params):
return 'Extraneous parameters', extraneous_params
return None, None
def _initialize_session(self, session_id): def _initialize_session(self, session_id):
if not self.sessions.get(session_id, False): if not self.sessions.get(session_id, False):
self._register_user_session(session_id) self._register_user_session(session_id)

View file

@ -1,6 +1,7 @@
Twisted==16.6.0 Twisted==16.6.0
appdirs==1.4.3 appdirs==1.4.3
argparse==1.2.1 argparse==1.2.1
docopt==0.6.2
base58==0.2.2 base58==0.2.2
git+https://github.com/lbryio/bumpversion.git#egg=bumpversion git+https://github.com/lbryio/bumpversion.git#egg=bumpversion
colorama==0.3.7 colorama==0.3.7

View file

@ -31,6 +31,7 @@ requires = [
'seccure', 'seccure',
'txJSON-RPC', 'txJSON-RPC',
'zope.interface', 'zope.interface',
'docopt'
] ]
console_scripts = [ console_scripts = [

View file

@ -16,23 +16,3 @@ class DaemonCLITests(unittest.TestCase):
self.assertEqual(False, DaemonCLI.guess_type('false')) self.assertEqual(False, DaemonCLI.guess_type('false'))
self.assertEqual(False, DaemonCLI.guess_type('False')) self.assertEqual(False, DaemonCLI.guess_type('False'))
def test_get_params(self):
test_params = [
'b64address=VdNmakxFORPSyfCprAD/eDDPk5TY9QYtSA==',
'name=test',
'amount=5.3',
'n=5',
'address=bY13xeAjLrsjP4KGETwStK2a9UgKgXVTXu',
't=true',
'f=False',
]
test_r = {
'b64address': 'VdNmakxFORPSyfCprAD/eDDPk5TY9QYtSA==',
'name': 'test',
'amount': 5.3,
'n': 5,
'address': 'bY13xeAjLrsjP4KGETwStK2a9UgKgXVTXu',
't': True,
'f': False,
}
self.assertDictEqual(test_r, DaemonCLI.get_params_from_kwargs(test_params))