From fe0abd7c06540638495e404a48f5f2dff5ce096c Mon Sep 17 00:00:00 2001 From: Joseph Zhong Date: Mon, 6 May 2019 14:19:23 -0400 Subject: [PATCH] cmd_line: update to latest version --- cmd_line.py | 121 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/cmd_line.py b/cmd_line.py index fa0c429..a54931c 100644 --- a/cmd_line.py +++ b/cmd_line.py @@ -8,35 +8,12 @@ """ import argparse import inspect -import logging -import subprocess from types import GeneratorType import src.utils.utility as _util -_logger = _util.getLogger("CMD Line") - -def runCmd(cmd, logger=None, stopOnFail=True): - if logger is None: - logger = _logger - else: - assert isinstance(logger, logging.Logger) - - logger.info("Running '%s'", cmd) - # output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT, shell=True) - # print output - # ret = subprocess.call(cmd.split(), shell=True) - # if ret != 0: - # logger.error("'%s' returned with error code: '%s'", cmd, ret) - # logger.debug("Traceback: '%s'", traceback.format_exc()) - # if stopOnFail: - # sys.exit(ret) - # else: - # logger.info("'{}' Success!".format(cmd)) - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=False) - proc_stdout = process.communicate()[0].strip() - print(proc_stdout) - logger.info("Completed running '%s", cmd) +_logger = _util.get_logger(__file__) + def _str_to_bool(s): """Convert string to bool (in argparse context).""" @@ -44,6 +21,7 @@ def _str_to_bool(s): raise ValueError('Need bool; got %r' % s) return {'true': True, 'false': False}[s.lower()] + def add_boolean_argument(parser, name, default=False): group = parser.add_mutually_exclusive_group() group.add_argument( @@ -56,27 +34,33 @@ def add_boolean_argument(parser, name, default=False): dest=name, action='store_false') + def parseArgsForClassOrScript(fn): assert inspect.isfunction(fn) or inspect.ismethod(fn) - spec = inspect.getargspec(fn) + sig = inspect.signature(fn) parser = argparse.ArgumentParser() - for i, arg in enumerate(spec.args): - if arg == 'self' or arg == 'logger': + for arg_name, arg in sig.parameters.items(): + if arg_name == 'self' or arg_name == 'logger': continue - # If index is greater than the last var with a default, it's required. - numReq = len(spec.args) - len(spec.defaults) - required = i < numReq - default = spec.defaults[i - numReq] if not required else None + # Arguments are required or not required, + # and have either an annotation or default value. # By default, args are parsed as strings if not otherwise specified. - if isinstance(default, bool): - parser.add_argument("--" + arg, default=default, action='store_true') - elif isinstance(default, (tuple, list, GeneratorType)): - parser.add_argument("--" + arg, default=default, nargs="+", help="Tuple of " + arg, required=False) + # REVIEW josephz: Is there a better way to determine if it is a positional/required argument? + required = arg.default is inspect.Parameter.empty + default = arg.default if arg.default is not inspect.Parameter.empty else None + type_ = arg.annotation if arg.annotation is not inspect.Parameter.empty else type(default) if default is not None else str + + if type_ is bool: + # REVIEW josephz: This currently has a serious flaw in that clients may only set positive boolean flags. + # The way to fix this would be to use the annotation to parse the input as a boolean. + parser.add_argument("--" + arg_name, default=default, action='store_true') + elif type_ in (tuple, list, GeneratorType): + parser.add_argument("--" + arg_name, default=default, type=type_, nargs="+", help="Tuple of " + arg_name) else: - parser.add_argument("--" + arg, default=default, type=type(default) if default is not None else str) + parser.add_argument("--" + arg_name, default=default, type=type_, required=required) parser.add_argument("-v", "--verbosity", default=_util.DEFAULT_VERBOSITY, @@ -91,24 +75,65 @@ def parseArgsForClassOrScript(fn): argv = parser.parse_args() argsToVals = vars(argv) - # Print args for any verbosity greater than CRITICAL. - if argv.verbosity > 0 or argv.help: + if argv.verbosity >= 0 or hasattr(argv, 'help'): docstr = inspect.getdoc(fn) if docstr is None: - print( -"""WARNING: - Please write documentation :) -""") + "WARNING: Please write documentation :)" print() print(docstr.strip()) print() print("Arguments and corresponding default or set values") - for arg in spec.args: - if arg == 'self' or arg == 'logger' or arg not in argsToVals: + for arg_name in sig.parameters: + if arg_name == 'self' or arg_name == 'logger' or arg_name not in argsToVals: continue - print("\t{}={}".format(arg, argsToVals[arg] if argsToVals[arg] is not None else "")) + print("\t{}={}".format(arg_name, argsToVals[arg_name] if argsToVals[arg_name] is not None else "")) print() - # parser.print_help() - return argv + +def test_cmdline(required, + required_anno: int, + required_anno_tuple: tuple, + not_required="Test", + not_required_bool=False, + not_required_int=123, + not_required_float=123.0, + not_required_tuple=(1,2,3), + not_required_str="123", + not_required_None=None, +): + """ + + :param required: A required, unannotated parameter. + :param required_anno: A required, annotated int parameter. + :param required_anno_tuple: A required, annotated tuple parameter. + :param not_required: A required, default-string parameter. + :param not_required_bool: A not-required, default-string parameter. + :param not_required_int: A not-required, default-int parameter. + :param not_required_float: A not-required, default-float parameter. + :param not_required_tuple: A not-required, default-tuple parameter. + :param not_required_str: A not-required, default-string parameter. + :param not_required_None: A not-required, default-string parameter. + """ + print("required:", required) + print("required_anno: ", required_anno) + print("required_anno_tuple:", required_anno_tuple) + print("not_required: ", not_required) + print("not_required_bool:", not_required_bool) + print("not_required_int:", not_required_int) + print("not_required_float:", not_required_float) + print("not_required_tuple:", not_required_tuple) + print("not_required_str:", not_required_str) + print("not_required_None:", not_required_None) + +def main(): + global _logger + args = parseArgsForClassOrScript(test_cmdline) + varsArgs = vars(args) + varsArgs.pop('verbosity', _util.DEFAULT_VERBOSITY) + _logger.info("Passed arguments: '{}'".format(varsArgs)) + test_cmdline(**varsArgs) + +if __name__ == '__main__': + main() +