Named arguments with Python argparse - python

I'm trying to create a terminal application a bit similar to cutechess-cli, which has options like the following:
-epdout FILE Save the end position of the games to FILE in FEN format.
-recover Restart crashed engines instead of stopping the match
-repeat [N] Play each opening twice (or N times). Unless the -noswap...
which can all be done with argparse in Python.
However it also has "named arguments" like the following:
-resign movecount=COUNT score=SCORE [twosided=VALUE]
Adjudicate the game as a loss if an engine's score is
at least SCORE centipawns below zero for at least COUNT
consecutive moves.
-sprt elo0=ELO0 elo1=ELO1 alpha=ALPHA beta=BETA
Use a Sequential Probability Ratio Test as a termination
criterion for the match. This option should only be used...
I can implement that with argparse using nargs='*' and then writing my own parser (maybe just regex). However that doesn't give nice documentation, and if argparse can already do something like this, I would rarther use the builtin approach.
Summary: Does argparse have a concept of named arguments similar to resign and sprt above? And if not, would the best approach be to do this manyally using nargs='*'?

You can use a custom type to split the values and use the metavar argument to give a better description for the value:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--arg', nargs='*', type=lambda text: text.split('=', maxsplit=1), metavar='PARAM-NAME=PARAM-VALUE', help='Some other parameters')
args = parser.parse_args()
args.arg = {k: v for k,v in args.arg}
Which produces:
usage: [-h] [--arg [PARAM-NAME=PARAM-VALUE [PARAM-NAME=PARAM-VALUE ...]]]
optional arguments:
-h, --help show this help message and exit
--arg [PARAM-NAME=PARAM-VALUE [PARAM-NAME=PARAM-VALUE ...]]
Some other parameters
If you wanted to you could avoid the "postprocessing" step to build the dictionary by using a custom Action type. But it seems an overkill to me in this case.

Related

Command line with Python

I'm developing a simple project with the purpose or learning Python, actually I have version 3.6 and I wanted to build a command line tool to generate password with specific criteria. So fare here is what I got:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "-auto", help="auto mode", action="store_true")
group.add_argument("-m", "-manual", help="manual mode", action="store_true")
parser.parse_args()
the dead end is I have no idea how to limit a command, example -l -lenght to the reqisite of having -m another stop is, how do I define -l so that it can accept a value, for example -l:8 to specify a password of 8 characters, I also want to put a range limit on -l values, example -l:8-256 and finally, I dont understand the difference between - and -- when defining the parameters.
I have already done all the part of generating passwords, just need a way to control its flow from outside so implementing parameters looked like a good way of doing this.
What you are looking for is the choices option.
add_argument('--length', type=int, choices=range(8,257)
This will only accept integer values between 8 and 256 (inclusive).
As for your second question, - indicates the shorthand for an option, which is common in most CLI tools, well -- indicates the long hand. It is common practice is CLI tools to provide both a long and a short hand for options.
You can define a custom type to check for valid values:
from argparse import ArgumentTypeError
def passwd_len(s):
try:
s = int(s)
except ValueError:
raise ArgumentTypeError("'%s' is not an integer" % (s,))
if not (8 <= s <= 256):
raise ArgumentTypeError("%d is not between 8 and 256, inclusive" % (s,))
return s
parser.add_argument("--length", "-l", type=passwd_len)
The difference between -- and - is one of conventions. Historically, options were single-letter arguments prefixed with a -, and groups of options could be combined: -ab was the same as -a -b. In order to
support that convention and allow longer option names, a new convention
of using -- to prefix multi-character names was devised. --length is a single option, not a group of six options -l, -e, -n, -g, -t, and -h. Long options cannot be grouped.
I would define the -a and -m options like this:
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "--auto", help="auto mode", action="store_const", const="auto", dest="mode")
group.add_argument("-m", "--manual", help="manual mode", action="store_const", const="manual", dest="mode")
group.add_argument("--mode", choices=["auto", "manual"])
Now instead of having two Boolean attributes that can never have the same value, you have just one attribute whose value you can check directly. Think of --mode as being the canonical way of choosing a mode, with -a and -m as shortcuts for selecting a specific mode.

Python - Difference between docopt and argparse

I have to write a command-line interface and I've seen I can use docopt and argparse.
I would like to know what are the main differences between the two so that I can make an enlightened choice.
Please stick to the facts. I don't want Wow. docopt. So beautiful. Very useful.
Docopt parses a doc string, whereas argparse constructs its parsing by creating an object instance and adding behaviour to it by function calls.
Example for argparse:
parser = argparse.ArgumentParser()
parser.add_argument("operation", help="mathematical operation that will be performed",
choices=['add', 'subtract', 'multiply', 'divide'])
parser.add_argument("num1", help="the first number", type=int)
parser.add_argument("num2", help="the second number", type=int)
args = parser.parse_args()
Example for docopt:
"""Calculator using docopt
Usage:
calc_docopt.py <operation> <num1> <num2>
calc_docopt.py (-h | --help)
Arguments:
<operation> Math Operation
<num1> First Number
<num2> Second Number
Options:
-h, --help Show this screen.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Calculator with docopt')
print(arguments)
Note, that docopt uses Usage: and Options: sections for parsing. Here Arguments: is provided only for end-user convenience.
The Why page for Click:
https://click.palletsprojects.com/en/7.x/why/
has a nice comparison between argparse, docopt and click itself.
Click is another command-line parsing utility for Python.
argparse is in the python default library, so this doesn't add any extra dependencies to your program. The differences are mainly the way of writing your code. Using argparse it is possible to add hooks for plugins so they can add their own argumnets to your program. For example flake8 uses this.
docopt is a third party module provides a simple way of parsing arguments. I personally like docopt because of its simplicity, but I'm not saying it is the best to use in all cases. In their documentation they mention that using docopt it is possible to use more combinations of argument passing than when using argparse.

Python's argparse choose one of several optional parameter

I have a program which can be used in the following way:
program install -a arg -b arg
program list
program update
There can only ever be one of the positional arguments specified (install, list or update). And there can only be other arguments in the install scenario.
The argparse documentation is a little dense and I'm having a hard time figuring out how to do this correctly. What should my add_arguments look like?
This seems like you want to use subparsers.
from argparse import ArgumentParser
parser = ArgumentParser()
subparsers = parser.add_subparsers()
install = subparsers.add_parser('install')
install.add_argument('-b')
install.add_argument('-a')
install.set_defaults(subparser='install')
lst = subparsers.add_parser('list')
lst.set_defaults(subparser='list')
update = subparsers.add_parser('update')
update.set_defaults(subparser='update')
print parser.parse_args()
As stated in the docs, I have combined with set_defaults so that you can know which subparser was invoked.

Python: argument parser that handles global options to sub-commands properly

argparse fails at dealing with sub-commands receiving global options:
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
will have p.parse_args('--arg test'.split()) work,
but fails on p.parse_args('test --arg'.split()).
Anyone aware of a python argument parser that handles global options to sub-commands properly?
You can easily add this argument to both parsers (main parser and subcommand parser):
import argparse
main = argparse.ArgumentParser()
subparser = main.add_subparsers().add_parser('test')
for p in [main,subparser]:
p.add_argument('--arg', action='store_true')
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
Edit: As #hpaulj pointed in comment, there is also parents argument which you can pass to ArgumentParser constructor or to add_parser method. You can list in this value parsers which are bases for new one.
import argparse
base = argparse.ArgumentParser(add_help=False)
base.add_argument('--arg', action='store_true')
main = argparse.ArgumentParser(parents=[base])
subparser = main.add_subparsers().add_parser('test', parents=[base])
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
More examples/docs:
looking for best way of giving command line arguments in python, where some params are req for some option and some params are req for other options
Python argparse - Add argument to multiple subparsers (I'm not sure if this question is not overlaping with this one too much)
http://docs.python.org/dev/library/argparse.html#parents
Give docopt a try:
>>> from docopt import docopt
>>> usage = """
... usage: prog.py command [--test]
... prog.py another [--test]
...
... --test Perform the test."""
>>> docopt(usage, argv='command --test')
{'--test': True,
'another': False,
'command': True}
>>> docopt(usage, argv='--test command')
{'--test': True,
'another': False,
'command': True}
There's a ton of argument-parsing libs in the Python world. Here are a few that I've seen, all of which should be able to handle address the problem you're trying to solve (based on my fuzzy recollection of them when I played with them last):
opster—I think this is what mercurial uses, IIRC
docopt—This one is new, but uses an interesting approach
cliff—This is a relatively new project by Doug Hellmann (PSF member, virtualenvwrapper author, general hacker extraordinaire) is a bit more than just an argument parser, but is designed from the ground up to handle multi-level commands
clint—Another project that aims to be "argument parsing and more", this one by Kenneth Reitz (of Requests fame).
Here's a dirty workaround --
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
def my_parse_args(ss):
#parse the info the subparser knows about; don't issue an error on unknown stuff
namespace,leftover=p.parse_known_args(ss)
#reparse the unknown as global options and add it to the namespace.
if(leftover):
s.add_parser('null',add_help=False)
p.parse_args(leftover+['null'],namespace=namespace)
return namespace
#print my_parse_args('-h'.split()) #This works too, but causes the script to stop.
print my_parse_args('--arg test'.split())
print my_parse_args('test --arg'.split())
This works -- And you could modify it pretty easily to work with sys.argv (just remove the split string "ss"). You could even subclass argparse.ArgumentParser and replace the parse_args method with my_parse_args and then you'd never know the difference -- Although subclassing to replace a single method seems overkill to me.
I think however, that this is a lit bit of a non-standard way to use subparsers. In general, global options are expected to come before subparser options, not after.
The parser has a specific syntax: command <global options> subcommand <subcommand ptions>, you are trying to feed the subcommand with an option and but you didn't define one.

Python: Allow the positional argument to be specified last or write it first in the help output when using argparse

This code
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('target', help='Specifiy who to attack!')
magic = ['fireball', 'heal', 'microwave']
parser.add_argument('-m', '--magic', nargs='*',
choices=magic,
help=('Magic'))
parsed_arguments = parser.parse_args()
Produces this help output
usage: Example.py [-h]
[-m [{fireball,heal,microwave} [{fireball,heal,microwave} ...]]]
target
positional arguments:
target Specifiy who to attack!
optional arguments:
-h, --help show this help message and exit
-m [{fireball,heal,microwave} [{fireball,heal,microwave} ...]], --magic [{fireball,heal,microwave} [{fireball,heal,microwave} ...]]
Magic
I think the help output is confusing and makes it look like the target should be specified last, which however does not work: python example.py -m fireball troll gives argument -m/--magic: invalid choice: 'troll'.
I realize the grammar of the language becomes ambiguous, but it would still be possible to tell that as there should exist one word (target) last in the sentence troll is not an argument to the -m option.
Questions:
Is there a way to let the positional argument be specified last without beating argparse to much?
Is there a way to let the argparse help output indicate that target should indeed be specified first?
As previously mentioned in a comment, the standard methods on POSIX systems to denote the end of options is to separate the arguments and options by --.
As for the second question: You might have to create your own HelpFormatter to achieve this, see formatter-class. You might be able to inherit from the default formatter and override only the necessary functions to generate the usage line.

Categories