(semi-) automatic generation of argparsers for functions

泪湿孤枕 提交于 2019-11-30 15:13:41

Have you tried plac?

An example in docs:

# dbcli.py
import plac
from sqlalchemy.ext.sqlsoup import SqlSoup

@plac.annotations(
    db=plac.Annotation("Connection string", type=SqlSoup),
    header=plac.Annotation("Header", 'flag', 'H'),
    sqlcmd=plac.Annotation("SQL command", 'option', 'c', str, metavar="SQL"),
    delimiter=plac.Annotation("Column separator", 'option', 'd'),
    scripts=plac.Annotation("SQL scripts"),
    )
def main(db, header, sqlcmd, delimiter="|", *scripts):
    "A script to run queries and SQL scripts on a database"
    yield 'Working on %s' % db.bind.url

    if sqlcmd:
        result = db.bind.execute(sqlcmd)
        if header: # print the header
            yield delimiter.join(result.keys())
        for row in result: # print the rows
            yield delimiter.join(map(str, row))

    for script in scripts:
        db.bind.execute(open(script).read())
        yield 'executed %s' % script

if __name__ == '__main__':
    for output in plac.call(main):
        print(output)

Output:

usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]

A script to run queries and SQL scripts on a database

positional arguments:
  db                    Connection string
  scripts               SQL scripts

optional arguments:
  -h, --help            show this help message and exit
  -H, --header          Header
  -c SQL, --sqlcmd SQL  SQL command
  -d |, --delimiter |   Column separator

Functionality akin to plac is provided by argh, which particularly features simple creation of subparsers (like those found in git or django-admin.py).

An example from its docs:

from argh import *

def dump(args):
    return db.find()

@command
def load(path, format='json'):
    print loaders[format].load(path)

p = ArghParser()
p.add_commands([load, dump])

if __name__ == '__main__':
    p.dispatch()

Produces the following --help response:

usage: prog.py [-h] {load,dump} ...

positional arguments:
  {load,dump}
    load
    dump

optional arguments:
  -h, --help   show this help message and exit

and the following with load --help:

usage: prog.py load [-h] [-f FORMAT] path

positional arguments:
  path

optional arguments:
  -h, --help            show this help message and exit
  -f FORMAT, --format FORMAT

Arguments may be annotated:

@arg('path')
@arg('--format', choices=['yaml','json'], default='json')
@arg('--dry-run', default=False)
@arg('-v', '--verbosity', choices=range(0,3), default=1)
def load(args, LOADERS={'json': json.load, 'yaml': yaml.load}):
    loader = loaders[args.format]
    data = loader(open(args.path))
    ...

And with @plain_signature, the args argument to load is expanded into keyword arguments:

@arg('path')
@arg('--format', choices=['yaml','json'], default='json')
@arg('--dry-run', default=False)
@arg('-v', '--verbosity', choices=range(0,3), default=1)
@plain_signature
def load(path, format, dry_run, verbosity):
    ...

You can use the inspect module to look at your own function definitions. This way you can at least write a rudimentary argparse skeleton. However, you will probably need more information than just the argument names and possibly default values.

For instance, you will also need to give a description. This info you can largely provide by creating a docstring in an appropriate format. There are parsers for docstrings (e.g. Sphynx) Using this extra information, I think you will be able to automatically generate argparse invocations for your functions.

I do not think a decorator is required, as probably all the information can be stored in your docstring.

Let me know how you fare, I am interested in the results of your project.

Another insteresting alternative is commando python module as declarative interface to argparse with additional utilities.

Example

Without commando:

def main():
    parser = argparse.ArgumentParser(description='hyde - a python static website generator',
                                  epilog='Use %(prog)s {command} -h to get help on individual commands')
    parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
    parser.add_argument('-s', '--sitepath', action='store', default='.', help="Location of the hyde site")
    subcommands = parser.add_subparsers(title="Hyde commands",
                                     description="Entry points for hyde")
    init_command = subcommands.add_parser('init', help='Create a new hyde site')
    init_command.set_defaults(run=init)
    init_command.add_argument('-t', '--template', action='store', default='basic', dest='template',
                     help='Overwrite the current site if it exists')
    init_command.add_argument('-f', '--force', action='store_true', default=False, dest='force',
                     help='Overwrite the current site if it exists')
    args = parser.parse_args()
    args.run(args)

def init(self, params):
    print params.sitepath
    print params.template
    print params.overwrite

With commando:

class Engine(Application):

    @command(description='hyde - a python static website generator',
            epilog='Use %(prog)s {command} -h to get help on individual commands')
    @param('-v', '--version', action='version', version='%(prog)s ' + __version__)
    @param('-s', '--sitepath', action='store', default='.', help="Location of the hyde site")
    def main(self, params): pass

    @subcommand('init', help='Create a new hyde site')
    @param('-t', '--template', action='store', default='basic', dest='template',
            help='Overwrite the current site if it exists')
    @param('-f', '--force', action='store_true', default=False, dest='overwrite',
            help='Overwrite the current site if it exists')
    def init(self, params):
        print params.sitepath
        print params.template
        print params.overwrite

The "least boilerplate" library I have found is fire (pip install fire).

Creating a command line parser for your example is as easy as:

import fire

def copy(foo, bar, baz):
...
def unlink(parrot, nomore=True):
...

if __name__ == '__main__':
    fire.Fire()

and this turns your module into "Fire" CLI:

python your_module.py copy sim sala bim
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!