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 os
import sys
import colorama
from docopt import docopt
from collections import OrderedDict
from lbrynet import conf
from lbrynet.core import utils
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 urllib2 import URLError, HTTPError
from httplib import UNAUTHORIZED
def main():
colorama.init()
parser = argparse.ArgumentParser(add_help=False)
_, arguments = parser.parse_known_args()
def remove_brackets(key):
if key.startswith("<") and key.endswith(">"):
return str(key[1:-1])
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()
api = LBRYAPIClient.get_client()
@ -44,94 +89,43 @@ def main():
print " Status: " + message
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
if method in ['--help', '-h', 'help']:
if len(params) == 0:
print_help(api)
elif 'command' not in params:
print_error(
'To get help on a specific command, use `{} help command=COMMAND_NAME`'.format(
os.path.basename(sys.argv[0]))
)
try:
result = api.call(method, **kwargs)
if isinstance(result, basestring):
# printing the undumped string is prettier
print result
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("'" + method + "' is not a valid command.")
print_error(error_data['error']['message'] + "\n", suggest_help=False)
else:
print_error("Something went wrong\n", suggest_help=False)
else:
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_help_for_command(method)
print_error(error_data['error']['message'] + "\n", suggest_help=False)
else:
print_error("Something went wrong\n", suggest_help=False)
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
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 guess_type(x):
if not isinstance(x, (unicode, str)):
return x
if x in ('true', 'True', 'TRUE'):
return True
if x in ('false', 'False', 'FALSE'):
@ -159,7 +153,8 @@ def print_error(message, suggest_help=True):
print_help_suggestion()
def print_help(api):
def print_help():
commands = Daemon.callable_methods.keys()
print "\n".join([
"NAME",
" lbrynet-cli - LBRY command line client.",
@ -170,20 +165,18 @@ def print_help(api):
"EXAMPLES",
" lbrynet-cli commands # list available commands",
" lbrynet-cli status # get daemon status",
" lbrynet-cli resolve_name name=what # resolve a name",
" lbrynet-cli help command=resolve_name # get help for a command",
" lbrynet-cli resolve_name what # resolve a name",
" lbrynet-cli help resolve_name # get help for a command",
"",
"COMMANDS",
wrap_list_to_term_width(api.commands(), prefix=' ')
wrap_list_to_term_width(commands, prefix=' ')
])
def print_help_for_command(api, command):
help_response = api.call('help', {'command': command})
print "Help for %s method:" % command
message = help_response['help'] if 'help' in help_response else help_response
message = "\n".join([' ' + line for line in message.split("\n")])
print message
def print_help_for_command(command):
fn = Daemon.callable_methods.get(command)
if fn:
print "Help for %s method:\n%s" % (command, fn.__doc__)
def wrap_list_to_term_width(l, width=None, separator=', ', prefix=''):

View file

@ -1,7 +1,7 @@
import logging
import urlparse
import inspect
import json
import inspect
from decimal import Decimal
from zope.interface import implements
@ -15,9 +15,9 @@ from traceback import format_exc
from lbrynet import conf
from lbrynet.core.Error import InvalidAuthenticationToken
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.client import LBRY_SECRET
from lbrynet.undecorated import undecorated
log = logging.getLogger(__name__)
@ -338,12 +338,16 @@ class AuthJSONRPCServer(AuthorizedBase):
if args == EMPTY_PARAMS or args == []:
args_dict = {}
_args, _kwargs = (), {}
elif isinstance(args, dict):
args_dict = args
elif len(args) == 1 and isinstance(args[0], dict):
# TODO: this is for backwards compatibility. Remove this once API and UI are updated
# TODO: also delete EMPTY_PARAMS then
args_dict = args[0]
_args, _kwargs = (), args
elif isinstance(args, list):
_args, _kwargs = args, {}
else:
# d = defer.maybeDeferred(function, *args) # if we want to support positional args too
raise ValueError('Args must be a dict')
@ -400,28 +404,6 @@ class AuthJSONRPCServer(AuthorizedBase):
(utils.now() - time_in).total_seconds()))
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):
"""
Add or update a HMAC secret for a session
@ -502,6 +484,28 @@ class AuthJSONRPCServer(AuthorizedBase):
self._verify_method_is_callable(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):
if not self.sessions.get(session_id, False):
self._register_user_session(session_id)

View file

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

View file

@ -31,6 +31,7 @@ requires = [
'seccure',
'txJSON-RPC',
'zope.interface',
'docopt'
]
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'))
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))