import os, sys, subprocess, inspect from dataclasses import dataclass from typing import Any from argparse import ArgumentParser @dataclass class Param(): "A parameter in a function used in `anno_parser` or `call_parse`" help:str=None type:type=None opt:bool=True action:str=None nargs:str=None const:str=None choices:str=None required:bool=None @property def pre(self): return '--' if self.opt else '' @property def kwargs(self): return {k:v for k,v in self.__dict__.items() if v is not None and k!='opt'} def anno_parser(func): "Look at params (annotated with `Param`) in func and return an `ArgumentParser`" p = ArgumentParser(description=func.__doc__) for k,v in inspect.signature(func).parameters.items(): param = func.__annotations__.get(k, Param()) kwargs = param.kwargs if v.default != inspect.Parameter.empty: kwargs['default'] = v.default p.add_argument(f"{param.pre}{k}", **kwargs) return p def call_parse(func): "Decorator to create a simple CLI from `func` using `anno_parser`" name = inspect.currentframe().f_back.f_globals['__name__'] if name == "__main__": args = anno_parser(func).parse_args() func(**args.__dict__) else: return func def call_plac(f): "Decorator to create a simple CLI from `func` using `plac`" name = inspect.currentframe().f_back.f_globals['__name__'] if name == '__main__': import plac res = plac.call(f) if callable(res): res() else: return f