Skip to content

Instantly share code, notes, and snippets.

@gvx
Created May 15, 2020 17:53
Show Gist options
  • Save gvx/0bda90ef7e73e1578441b8250c5cdd3a to your computer and use it in GitHub Desktop.
Save gvx/0bda90ef7e73e1578441b8250c5cdd3a to your computer and use it in GitHub Desktop.
simple argparse wrapper
from inspect import signature, Signature, Parameter
from argparse import ArgumentParser
POSITIONAL = (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
def parse_args(parser, argv=None):
return parser.parse_args(argv).__dict__
class Command:
def __init__(self, f, parser):
self.f = f
self.parser = parser
self.sig = signature(f).parameters.values()
self.varargs = None
for param in self.sig:
kwargs = {}
if param.kind is Parameter.KEYWORD_ONLY:
name = f'--{param.name.replace("_", "-")}'
kwargs['dest'] = param.name
else:
name = param.name
kwargs['metavar'] = name.replace("_", "-").upper()
if param.kind is Parameter.VAR_POSITIONAL:
kwargs['nargs'] = '*'
self.varargs = name
elif param.default is not Parameter.empty:
kwargs['nargs'] = '?'
if param.default is not Parameter.empty:
kwargs['default'] = param.default
if param.annotation is bool:
kwargs['action'] = 'store_false'
self.parser.add_argument(f'--no-{param.name.replace("_", "-")}', **kwargs)
kwargs['action'] = 'store_true'
elif param.annotation is not Parameter.empty:
kwargs['type'] = param.annotation
self.parser.add_argument(name, **kwargs)
def extract_args(self, arg_dict):
args = [arg_dict[param.name] for param in self.sig if param.kind in POSITIONAL]
if self.varargs:
args += arg_dict[self.varargs]
kwargs = {param.name: arg_dict[param.name] for param in self.sig if param.kind is Parameter.KEYWORD_ONLY}
return args, kwargs
def extract_and_call(self, args):
args, kwargs = self.extract_args(args)
return self.f(*args, **kwargs)
def __call__(self, argv=None):
return self.extract_and_call(parse_args(self.parser, argv))
def run_subcommands(fs, help=None):
parser = ArgumentParser(description=help)
subparsers = parser.add_subparsers()
for f in fs:
sub = subparsers.add_parser(f.__name__, help=f.__doc__)
sub.set_defaults(_command_=Command(f, sub))
args = parse_args(parser)
return args.pop('_command_').extract_and_call(args)
def run(f):
return Command(f, ArgumentParser(description=f.__doc__))()
from coco import run
from pathlib import Path
def main(req_arg, optional_arg: int=6, *args: Path, option: bool=False):
'''my command help'''
print(req_arg, optional_arg, option, args)
if __name__ == '__main__':
run(main)
from coco import run_subcommands
def add(name: str):
print('adding', name)
def search(*, house: bool=True, work: bool=False):
print('searching', house and 'house' or '', work and 'work' or '')
if __name__ == '__main__':
run_subcommands([add, search],'some commands')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment