File size: 1,590 Bytes
cc9dfd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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