Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(semi-) automatic generation of argparsers for functions

tldnr: given a function, is there a way to automatically create an ArgumentParser from its signature?

I've got a bunch of functions that I'd like to expose to the command line. So basically, a module:

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

 if __name__ == '__main__':
     argparse stuff

which can be called from the command line like this:

 python commands.py move spam ham
 python commands.py unlink --parrot Polly

Although this is pretty straightforward to implement, there's a lot of wiring involved:

parser = argparse.ArgumentParser(...)
subparsers = parser.add_subparsers()
...
c = subparsers.add_parser('unlink', description='Unlink a parrot')
c.add_argument('--parrot', help='parrots name', required=True)
c.add_argument('--nomore', help='this parrot is no more', action='store_true')
...
c = subparsers.add_parser('move', description='Move stuff')
...

and so on, for each function. The worst thing, should function arguments change (and they do), the argparse stuff needs to be synchronized manually.

It would be much nicer if the functions could provide argparse stuff for themselves, so that the main code would be like:

parser = argparse.ArgumentParser(...)
subparsers = parser.add_subparsers()

copy.register(subparsers)
move.register(subparsers)
unlink.register(subparsers)
...

I thought of something along these lines:

@args(
    description='Unlink a parrot',
    parrot={'required':True, 'help':'parrots name'},
    nomore={'action': 'store_true', 'help': 'this parrot is no more'}
)
def unlink(parrot, nomore=True):
    ...

My questions:

  • is there a library that does something like this?
  • if no, is it possible to write such a decorator, and how?
  • is there a other/better way to implement what I want?

Upd:

plac appears to be the solution. Here's how to do what I want with plac:

commands module: cmds.py:

import plac

@plac.annotations(
    foo=('the foo thing'),
    bar=('the bar thing'),
    fast=('do a fast copy', 'flag')
)
def copy(foo, bar, fast=False):
    """Copy some foo to bar."""
    pass
        
@plac.annotations(
    parrots=('parrots names'),
    nomore=('these parrots are no more', 'flag'),
    repeat=('repeat n times', 'option', 'r', int)
)
def unlink(nomore=False, repeat=1, *parrots):
    """Unlink some parrots."""
    pass

#more commands...

# export commands so that plac knows about them
commands = 'copy', 'unlink'

and here's the main module:

import plac
import cmds

plac.call(cmds)

Quite neat if you ask me.

like image 412
georg Avatar asked Nov 06 '12 09:11

georg


1 Answers

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
like image 184
Jakub Kukul Avatar answered Sep 20 '22 00:09

Jakub Kukul