Subcommand Alternative To Argparse And Optparse
Solution 1:
You can get pretty close to your requested output just by changing your argparse code a little bit:
- Set the usage text by specifying the
usage
parameter toArgumentParser
. - Omit the
description
andhelp
arguments toadd_subparsers
. - Change the
title
parameter toAvailable subcommands
. - Use the
metavar
parameter to override the unsightly{foo,bar}
text. - Use the
help
arguments available inadd_parser
.
Here's the finished product:
import argparse
parser = argparse.ArgumentParser(usage='tool [command] [options]')
subparsers = parser.add_subparsers(title='Available commands', metavar='')
subparsers.add_parser('foo', help='foo.')
subparsers.add_parser('bar', help='bar.')
parser.parse_args(['-h'])
That code prints this:
usage: tool [command] [options] optional arguments: -h, --help show this help message and exit Available commands: foo foo. bar bar.
Solution 2:
Sounds like you are looking for argh
.
Here's a snippet from the presentation on the home page.
A potentially modular application with multiple commands:
import argh
# declaring:defecho(text):
"Returns given word as is."return text
defgreet(name, greeting='Hello'):
"Greets the user with given name. The greeting is customizable."return greeting + ', ' + name
# assembling:
parser = argh.ArghParser()
parser.add_commands([echo, greet])
# dispatching:if __name__ == '__main__':
parser.dispatch()
Of course it works:
$ ./app.py greet Andy
Hello, Andy
$ ./app.py greet Andy -g Arrrgh
Arrrgh, Andy
The help message on the site is slightly abridged. Here is what it actually outputs for me (argh
0.26.1).
$ ./app.py --help
usage: app.py [-h] {greet,echo} ...
positional arguments:
{greet,echo}
echo Returns given word as is.
greet Greets the user with given name. The greeting is customizable.
optional arguments:
-h, --help show this help message and exit
Solution 3:
Does this win the prize? :)
Custom parameters
Rob Kennedy has a better customization.
In [158]: parser=argparse.ArgumentParser(usage='tool [command] [options]',
description="Available commands:\n\n foo foo.\n bar bar.\n",
epilog= 'Use"tool help" to get full list of supported commands',
formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False)
In [159]: parser.print_help()
usage: tool [command] [options]
Available commands:
foo foo.
bar bar.
Use"tool help" to get full list of supported commands
What I've done is customize the help
with available parameters.
Alternative API and/or parser?
But your other lines, the parse.add()
ones suggest you don't like the argparse
method of defining 'commands'. You could add some methods to your parser that use this more compact syntax, but still end up calling the existing subparser
mechanism.
But maybe you want to replace the whole parsing scheme with your own. One, for example, that expects the first argument to be a 'command'. What about other 'positionals'? Who or what handles the 'options'?
Do you realize that the argparse
subparser scheme is built on top of the more basic optionals
and positionals
parsing scheme. The parser.add_subparsers
command is a specialized form of add_argument
. The subparsers
object is a positional argument, with a special Action class. {foo,bar}
is actually a list of the choices
values that you defined for this argument (names or aliases of the subcommands). The subcommands themselves are parsers.
Custom front end command parser
If the sys.argv[1]
item will always be a command
name, you could set up something like this:
if sys.argv[1:]:
cmd = sys.argv[1]
rest = sys.argv[2:]
parser = parser_dict.get(cmd, None)
if parser:
args = parser.parse_args(rest)
else:
print_default_help()
Where parser_dict
is a dictionary matching cmd
strings to defined parsers. In effect this is just a front end that captures the first argument string, and dispatches the handling of the rest to other defined parsers. They could be a mix of argparse
, optparse
, and custom parsers. This front end does not have to be fancy if all it handles is the first 'command' string.
print_default_help
would be little more than a pretty print of the parser_dict
.
On further thought, I realized that the sp.choices
attribute of an argparse
subparsers object is just such a dictionary - with command strings as keys, and parsers as values.
Custom format_help methods
Here are a couple of custom help formatters.
A simple one that only gets the prog
and _choices_actions
from the parser
. subparsers._choices_actions
is a list of objects that contain help and aliases information for the individual sub parsers.
defsimple_help(parser, subparsers):
# format a help message with just the subparser choices
usage = "Usage: %s command [options]"%parser.prog
desc = "Available commands:\n"
epilog = '\nUse "%s help" to get full list of supported commands.'%parser.prog
choices = fmt_choices(subparsers._choices_actions)
astr = [usage]
astr.append(desc)
astr.extend(choices)
astr.append(epilog)
return'\n'.join(astr)
deffmt_choices(choices):
# format names and help in 2 columns
x = max(len(k.metavar) for k in choices)
fmt = ' {:<%s} {}'%x
astr = []
for k in choices:
# k.metavar lists aliases as well
astr.append(fmt.format(k.dest, k.help))
return astr
This one is modeled on parser.format_help
, and makes uses of the Formatter
and all of its wrapping and spacing information. I wrote it to use non-default parameters where possible. It is hard, though, to suppress blank lines.
defspecial_help(parser, subparsers=None, usage=None, epilog=None):
# format help message using a Formatter# modeled on parser.format_help# uses nondefault parameters where possibleif usage isNone:
usage = "%(prog)s command [options]"if epilog isNone:
epilog = "Use '%(prog)s help' for command list"if subparsers isNone:
# find the subparsers action in the parserfor action in parser._subparsers._group_actions:
ifhasattr(action, '_get_subactions'):
subparsers = action
break# if none found, subparsers is still None?if parser._subparsers != parser._positionals:
title = parser._subparsers.title
desc = parser._subparsers.description
else:
title = "Available commands"
desc = Noneif subparsers.metavar isNone:
subparsers.metavar = '_________'# restore to None at end?
formatter = parser._get_formatter()
if parser.usage isNone:
formatter.add_usage(usage, [], [])
else:
formatter.add_usage(parser.usage,
parser._actions, parser._mutually_exclusive_groups)
# can I get rid of blank line here?
formatter.start_section(title)
formatter.add_text(desc)
formatter.add_arguments([subparsers])
formatter.end_section()
formatter.add_text(epilog)
return formatter.format_help()
These could be invoked in different ways. Either could replace the parser's
format_help
method, and thus be produced by the -h
option, as well as with parser.print_help()
.
Or you could include a help
subcommand. This would fit with the epilog
message. -h
would still produce the full, ugly help.
sp3 = sp.add_parser('help') # help='optional help message'
and test args
:
if args.cmd in ['help']:
print(simple_help(parser, sp))
# print(special_help(parser))
Another option is to check sys.argv
before parser.parser_args
, and call the help function if that list isn't long enough, or includes a help
string. This is roughly what Ipython
does to bypass the regular argparse
help.
Solution 4:
I'm not sure that I understand what's wrong with what you describe. I use something slightly different though:
parser = argparse.ArgumentParser(description='My description')
parser.add_argument('-i', '--input', type=str, required=True, help='Inputfile')
parser.add_argument('-o', '--output', type=str, required=False, help='Output file')
args = parser.parse_args()
input_filename = args.inputifnot args.output:
output_filename = input_filename
else:
output_filename = args.output
Solution 5:
You should take a look at Click. From the documentation, Click...
- is lazily composable without restrictions
- fully follows the Unix command line conventions
- supports loading values from environment variables out of the box
- supports for prompting of custom values
- is fully nestable and composable
- works the same in Python 2 and 3
- supports file handling out of the box
- comes with useful common helpers (getting terminal dimensions, ANSI colors, fetching direct keyboard input, screen clearing, finding config paths, launching apps and editors, etc.)
Arguments and options are pretty intuitive to create with decorators. You can create subcommands by creating groups as shown below.
import click
@click.command() @click.option('--count', default=1, help='number of greetings') @click.argument('name') defhello(count, name):
for i inrange(count):
print(f"{i}. Hello {name}")
@click.group() defcli():
pass@cli.command() definitdb():
click.echo('Initialized the database')
@cli.command() defdropdb():
click.echo('Dropped the database')
if __name__ == "__main__":
cli()
The output from this code is:
$ python click-example.py --help
Usage: click-example.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
dropdb
initdb
Post a Comment for "Subcommand Alternative To Argparse And Optparse"