argdeco¶
argdeco – use argparse with decorators
This module is main user interface.
Quickstart¶
If you want to create a simple program:
from argdeco import main, arg, opt
@main(
arg('--first', help="first argument"),
opt('--flag-1', help="toggle 1st thing"),
opt('--flag-2', help="toggle 2nd thing"),
)
def my_main(first, flag_1, flag_2):
# do something here
pass
if __name__ == "__main__":
main()
If you want to create a program with subcommands:
from argdeco import main, command, arg, opt
@command('cmd1')
def cmd1(global_arg):
print("this is the first command")
@command("cmd2", arg("--foo-bar"))
def cmd2(global_arg, foo_bar):
print("this is the second command with arg: %s" % foo_bar)
if __name__ == '__main__':
main(arg('--global-arg', help="a global arg applied to all commands"))
Compiling arguments¶
If you have many arguments it may get cumbersome to list all the arguments in the decorator and the function:
@command('cmd1',
arg('--first', '-f'),
arg('--second', '-s'),
arg('--third', '-t'),
arg('--fourth', '-F'),
)
def cmd1(first, second, third, fourth):
pass
Think of having many of such functions maybe even repeating some arguments. Then it becomes handy to use compiled arguments:
from argdeco import main, command
@command('cmd',
arg('--first', '-f'),
arg('--second', '-s'),
arg('--third', '-t'),
arg('--fourth', '-F'),
)
def cmd1(opts):
if opts['first'] == '1':
...
if __name__ == '__main__':
main(compile=True)
compile
can have following values:
Value | Alias | Description |
---|---|---|
None | ‘kwargs’ | Passed to handler as keyword arguments |
True | ‘dict’ | Args passed to handler as single dictionary |
‘args’ | Pass args namespace as returned from argparse.ArgumentParser.parse_args() |
|
function | You can also pass a function, which is explained in Compile functions |
Compile to args:
from argdeco import main, command
@command('cmd',
arg('--first', '-f'),
arg('--second', '-s'),
arg('--third', '-t'),
arg('--fourth', '-F'),
)
def cmd1(args):
if args.first == '1':
...
if __name__ == '__main__':
main(compile='args')
Compile functions¶
If you need even more control of your arguments, you can pass custom compile functions, which gets args namespace and opts keyword arguments as parameter and is expected to return:
type | description |
---|---|
dict | This will be passed as keyword arguments to handler function |
tuple/list | A tuple with two values, a list (or tuple) and a dictionary, which are passed as args and kwargs to handler. |
list/tuple | If the tuple or list does not match requirements in above, it is assumed, that no kwargs shall be passe and this is the args list for positional parameters. |
You can use such a function work preprocessing some args and manipulate the parameters passed to the handlers.
Compiler factory¶
Compile functions are usually not directly connected with command decorator and usually do not know about it (unless you share it globally). If you need to access data from command decorator instance or need for other reasons more control of argument setup, you can use a compiler factory.
A compiler factory is initialized with CommandDecorator
instance.
It must return a function, which will get args as returned from
argparse.ArgumentParser.parse_args()
and keyword arguments.
Here you see the most simplest one:
def my_factory(command):
def my_compiler(args, **opts):
return opts
return my_compiler
Validating and transforming arguments¶
With argparse.Action
argparse
module provides a method
to provide custom argument handlers. argdeco
provides some eases
for this as well:
from argdeco import arg, command, main
import dateutil.parser
@arg('--date', '-d')
def arg_date(value):
return dateutil.parser.parse(value)
@main(arg_date)
def handle_date(date)
print(date.isoformat())
main()
There is also a complex (more powerful) way, which is
import dateutil.parser
from argdeco import arg, command, main
@arg("-d", "--date", help="pass some date")
def arg_date(self, parser, namespace, values, option_string=None):
# here we can do some validations
print "self: %s" % self
setattr(namespace, self.dest, dateutil.parser.parse(values))
@command("check_date", arg_date)
def check_date(date):
print(date)
main()
Working with subcommands¶
You may want to implement a CLI like git has. This is quite easy with
argdeco
:
from argdeco import main, command, arg, opt
from textwrap import dedent
# we will implement a sample `remote` command here
# global arguments (for all commands)
main(
arg('--config-file', '-C', help="pass a config file"),
)
# create a new decorator for sub-command 'remote' actions
remote_command = command.add_subcommands('remote',
help="manage remote sites", # description in global command list
subcommands = dict(
title = "remote commands", # caption of subcommand list
description = dedent(''' Here is some documentation about remote commands.
There is a lot to say ...
''')
)
)
@remote_command('add',
arg('remote_name', help="name of remote site"),
arg('url', help="url of remote site"),
opt('--tags', help="get all tags when requesting remote site"),
)
def cmd_remote_add(config_file, remote_name, url, tags):
...
@remote_command('rename',
arg('old_name', help="old name of remote"),
arg('new_name', help="new name of remote"),
)
def cmd_remote_rename(config_file, old_name, new_name):
...
If you run add_subcommands(..., subcommands={...})
, all the
keyword arguments of add_subcommands, except the subcommands
one, will be passed
to argparse.ArgumentParser.add_parser()
and the subcommands dictionary
will be passed as keyword arguments to
argparse.ArgumentParser.add_subparsers()
.