I've got a command line interface for a Python program that has a bunch of options (say, --a, --b, --c) but one switches between commands with other switches.
So, perhaps prog -S a b c invokes the -S action, and prog -Y a b c invokes the -Y action. prog -Y a b c --a=2 --b=3, then, should invoke the -Y action with parameters a and b and positional argument a, b, c
Is there any way to make argparse or getopt do the argument parsing for me? Is there some other library to do this nicely?
I think using argparse's subcommands would be useful in this case.
Basically you can create a main parser that takes care of the parsing of the subcommand together with some common general options and then a few subparsers (one for each subcommand) that take care of the parsing of the specific options passed to the subcommands.
I'm not entirely sure if this will help, but so far, I have been writing a wrapper that takes arguments from XML set by a web interface, and then passes them into the command:
Obviously takes more complicated argument strings, but for the sake of an example:
def __main__():
parser = optparse.OptionParser()
parser.add_option( '-Q', '--ibmax', dest='ibmax', help='' )
(options, args) = parser.parse_args()
if options.ibmax != 'None' and int( options.ibmax ) >= 1:
ibmax = '--bmax %s' % options.ibmax
cmd1 = Popen([another.py, '-Q "%s"' % (options.ibmax),], stdout=PIPE).communicate()[0]
process = subprocess.Popen(cmd1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Depending on certain flags in my web interface, more options are added to the arg list and thus a different command is run. Add every command option to the parser and then check the value of the -Y or -S command to set vars and change which command you need to pass.
I hope this helps, I'm no python pro, this just works for me.
Related
Using argparse, is there a simple way to specify arguments which are mutually exclusive so that the application asks for one of these arguments have to be provided but only one of them?
Example of fictive use-case:
> myapp.py foo --bar
"Foo(bar) - DONE"
> myapp.py read truc.txt
"Read: truc.txt - DONE"
>myapp.py foo read
Error: use "myapp.py foo [options]" or "myapp.py read [options]" (or something similar).
> myapp.py foo truc.txt
Error: "foo" action don't need additional info.
> myapp.py read --bar
Error: "read" action don't have a "--bar" option.
My goal is to have a "driver" application(1) that would internally apply one action depending on the first command line argument and have arguments depending on the action.
So far I see no obvious ways to do this with argparse without manually processing the arguments myself, but maybe I missed something Pythonic? (I'm not a Python3 expert...yet)
I call it "driver" because it might be implemented by calling another application, like gcc does with different compilers.
What you're trying to do is actually supported quite well in Python.
See Mutual Exclusion
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('foo', dest='foo', nargs=1)
group.add_argument('read', dest='read', nargs=1)
args = parser.parse_args()
return args
Let's say I want to make a hashing script:
### some code here
def hashlib_based(path, htype='md5', block_size=2**16):
hash = eval(htype)
with open(path, 'rb') as f:
for block in iter(lambda: f.read(block_size), ''):
hash().update(block)
f.close()
return hash().hexdigest()
### some code here
As you can see, I have the opportunity to use different flags to allow me to change the hash type or the block size when I call the script from the command line (for example ./myscript.py -sha1 -b 512 some_file.ext). The thing is, I don't have any clue on how should I do this in order to keep my code as clean and readable as possible. How do I deal with sys.argv?
First of all, how do I check if the user uses the correct flags? I need to do that in order to print out a usage message. Do I make a list with all the flags, then I check if the user uses one that is in that list?
Should I do all these things inside main() or should I do place them in a different function?
Should I construct my flags with a hyphen-minus in front of them (like this: -a, -b) or without one? To check if a certain flag is present in sys.argv, do I simply do something like:
if '-v' in sys.argv:
verbose = True
?
Because sys.argv has indexes, what is the best way to ignore the order of the flags - or in other words, should ./myscript.py -a -b be the same as ./myscript.py -b -a? While it certainly makes the job easier for the common user, is it common practice to do so?
I saw something similar but for C#. Is there a similar concept in Python?
The thing is, as simple as these things are, they get out of hands quickly - for me at least. I end up doing a mess. What is your approach to this problem?
For really simple use cases, such as checking the presence of one argument, you can do a check like you're showing, i.e.:
if '-v' in sys.argv: ...
which is the quick'n dirty way of checking arguments. But once your project gets a bit more serious, you definitely need to use an argument parsing library.
And there are a few ones to handle argument parsing: there is the now deprecated getopt (I won't give a link), the most common one is argparse which is included in any python distribution.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--a-long', help='a help')
parser.add_argument('-b', '--b-long', help='b help')
args = parser.parse_args()
then you can call your script -a -b or script -b -a which will be equivalent. And for free, you've got script -h for free! :-)
Though, my preference is now over docopt, imho, which is way simpler and more elegant, for the same example:
"""
My script.
usage:
myscript -a | --along
myscript -b | --blong
Options:
-a --along a help
-b --blong b help
"""
from docopt import docopt
arguments = docopt(__doc__, version='myscript 1.0')
print(arguments)
HTH
I would like to make the parser like cmd [-a xxx -b xxx] -c xxx -d xxx
When -a is used, I want -b to be used too. likewise, if -b is used, -a must be used too. It's ok both -a and -b are not used.
How do I do that? I have tried custom actions, but it does not go well.
A better design would be to have a single option that takes two arguments:
parser.add_argument('-a', nargs=2)
Then you either specify the option with 2 arguments, or you don't specify it at all.
$ script -a 1 2
or
$ script
A custom action (or postprocessing) can split the tuple args.a into two separate values args.a and args.b.
Argparse doesn't natively support this type of use.
The most effective thing to do is check and see if those types of conditions are met after parsing:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-b')
parser.add_argument('-c')
args = parser.parse_args()
required_together = ('b','c')
# args.b will be None if b is not provided
if not all([getattr(args,x) for x in required_together]):
raise RuntimeError("Cannot supply -c without -b")
I have a program that uses argparse to process the command line.
The program's command line and hence it's help becomes context sensitive.
I would like to make the help reflect that context sensitivity.
e.g.
prog --mode=1 OPTA OPTB OPTC<br>
prog --mode=2 OPTD OPTE OPTF<br>
prog --mode=1 -h<br>
"In mode 1 you have four options, A,B,C,D"
prog --mode=2 -h<br>
"You mode 2 you have four options, D,E,F,G"
I should add here that this is only an example. In my actual program there could be any number of modes and they are not defined by my code, they are defined by users of my API. Therefore it is impossible to hard code the help for each mode. The actual help text is defined later.
This means altering the help strings for the argument 'option' to reflect the different modes after the --mode argument has been processed. The code, below, basically works in that the command works as expected, but the help does not.
The problem is that parse_known_args() seems to handle the -h then exit. I need parse_args() to handle the help. Obviously I could simple parse sys.argv and find --mode myself, but surely that defeats the object of argparse.
import argparse
parser = argparse.ArgumentParser(description='Test argparser')
parser.add_argument('--mode', nargs=1, type=int,
default=[1],
help='program mode')
options={
1:["OPTA","OPTB","OPTC","OPTD"],
2:["OPTD","OPTE","OPTF","OPTG"]}
args = parser.parse_known_args()[0]
print "Initial pass"
print args
parser.add_argument('options', type=str, nargs='+',
choices=options[args.mode[0]]+["ALL"],
default="ALL",
help='One or more of the options, above')
args = parser.parse_args()
print "Second pass"
print args
What you want to do is handled by argparse's sub-commands. Using sub-commands would imply replacing your --mode option with a sub-command:
prog --mode=1 OPTA OPTB OPTC
would become
prog mode1 OPTA OPTB OPTC
The mode1 sub-command can be given its own help; it is accessed with
prog mode1 -h
Another advantage of this approach is that prog -h lists the possible sub-commands (and an associated description).
Is there a Python module for doing gem/git-style command line arguments? What I mean by gem/git style is:
$ ./MyApp.py
The most commonly used MyApp commands are:
add Add file contents to the index
bisect Find by binary search the change that introduced a bug
branch List, create, or delete branches
checkout Checkout a branch or paths to the working tree
...
$ ./MyApp.py branch
* current-branch
master
With no arguments, the output tells you how you can proceed. And there is a special "help" command:
$ ./MyApp.py help branch
Which gets you deeper tips about the "branch" command.
Edit:
And by doing I mean it does the usage printing for you, exits with invalid input, runs your functions according to your CLI specification. Sort of a "URL mapper" for the command line.
Yes, argparse with add_subparsers().
It's all well explained in the Sub-commands section.
Copying one of the examples from there:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers()
>>> checkout = subparsers.add_parser('checkout', aliases=['co'])
>>> checkout.add_argument('foo')
>>> parser.parse_args(['checkout', 'bar'])
Namespace(foo='bar')
Edit: Unfortunately there's no self generated special help command, but you can get the verbose help message (that you seem to want) with -h or --help like one normally would after the command:
$ ./MyApp.py branch --help
By verbose I don't mean that is like a man page, it's like every other --help kind of help: listing all the arguments, etc...
Example:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(description='Sub description')
>>> checkout = subparsers.add_parser('checkout', description='Checkout description')
>>> checkout.add_argument('foo', help='This is the foo help')
>>> parser.parse_args(['checkout', '--help'])
usage: checkout [-h] foo
Checkout description
positional arguments:
foo This is the foo help
optional arguments:
-h, --help show this help message and exit
If you need to, it should be easy to implement an help command that redirects to --help.
A reasonable hack to get the gem/git style "help" behavior (I just wrote this for what I'm working on anyway):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sub_commands')
parser_branch = subparsers.add_parser('branch', description='list of branches')
parser_help = subparsers.add_parser('help')
parser_help.add_argument('command', nargs="?", default=None)
# I can't find a legitimate way to set a default subparser in the docs
# If you know of one, please let me know!
if len(sys.argv) < 2:
sys.argv.append('--help')
parsed = parser.parse_args()
if parsed.sub_commands == "help":
if not parsed.command:
parser.parse_args(['--help'])
else:
parser.parse_args([parsed.command, '--help'])
argparse is definitely a step up from optparse and other python solutions I've come across. But IMO the gem/git style of handling args is just a more logical and safer way to do things so it's annoying that it's not supported.
I wanted to do something similar to git commands, where I would load a second script based off of one of the command line options, and have that script populate more command line options, and also have the help work.
I was able to do this by disabling the help option, parse known args, add more arguments, re-enable the help option, and then parse the rest of the arguments.
This is what I came up with.
import argparse
#Note add_help=False
arg_parser = argparse.ArgumentParser(description='Add more arguments after parsing.',add_help=False)
arg_parser.add_argument('MODE', default='default',type=str, help='What commands to use')
args = arg_parser.parse_known_args()[0]
if args.MODE == 'branch':
arg_parser.add_argument('-d', '--delete', default='Delete a branch')
arg_parser.add_argument('-m', '--move', default='move a branch')
elif args.MODE == 'clone' :
arg_parser.add_argument('--local', '-l')
arg_parser.add_argument('--shared')
#Finally re-enable the help option, and reparse the arguments
arg_parser.add_argument(
'-h', '--help',
action='help', default=argparse.SUPPRESS,
help=argparse._('show this help message and exit'))
args = arg_parser.parse_args()