I have a question about argparse.
Here is part of my code:
(...)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-g', '--gfirst', dest="a", type=int, required=True)
args = parser.parse_args()
print args.a #Testing
print args.a #Testing
if __name__ == '__main__':
main()
print "3"
unittest.main(verbosity=2)
print "4"
(...)
I am trying to set 'a' as a required value to execute the test cases, because I will need this value in the future. However...
$ python regular_test.py --gfirst 2
2
2
3
option --gfirst not recognized
Usage: regular-test.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
...as you can see, the program accepts the argument and prints it, but the test case itself does not execute. I've inserted some prints to show whats executing and what isnt.
What am I doing wrong?
Thanks in advance
ps.: I am using python 2.7.3
ps2.:The tests were running properly (before adding argparse to the program.)
unittest.main() itself parses command-line arguments and it cannot understand/recognize your custom defined arguments (see parseArgs() method of the TestProgram class).
Instead, run tests using TextTestRunner:
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(unittest.makeSuite(MyTestCase))
Also see related threads:
argparse and unittest python
Call a python unittest from another script and export all the error messages
Your problem is that the unit-tester wants to own any command-line arguments. It might make it complicated to prescribe your own arguments.
Technically, unit-tests should contain everything they need in order to run, and shouldn't depend on arguments. You might consider moving any environment-related configuration (like a DB hostname, for example) to environment variables.
My two cents.
Related
I tried to run the following script to know what's inside the namespace returned by command.parse_args().
#!/usr/bin/python3
import argparse
command = argparse.ArgumentParser()
command.add_argument("test")
print("test1")
args = command.parse_args()
print("test2")
print(args)
If I run a "complete" command (like ./test.py 1) everything is ok and the Namespace is displayed.
But if I do something like ./test.py -h it fails and stops at args =
command.parse_args(), print("Test2") isn't called.
I got no error. But the rest of the script isn't interpreted.
Why?
Is there some kind of error this way?
Is ./test.py -h invalid for some reason (Even if it seems to work fine)?
Does args = command.parse_args() cause an exit before the script's end?
By default, help options are added to the argument parser. Then, when the help argument is passed, the help action is invoked which will print the help, and then end the program execution.
If you don’t want that to happen, you need to set the add_help argument to False but that will also stop the argument parser from providing a help functionality.
The exit behavior is pretty obvious if you actually look at the help text that is printed:
-h, --help show this help message and exit
Take a look at argparse.py source code.
https://github.com/python/cpython/blob/master/Lib/argparse.py#L1007
The argparse has the default help -h, which is an action to show the help and exit.
The _HelpAction.__call__ handle this action.
I have a commandline script that works perfectly fine. Now I want to make my tool more intuitive.
I have:
parser.add_argument("-s",help = "'*.sam','*.fasta','*.fastq'", required=True)
right now, python script.py -s savefile.sam works but I would like it to be python script.py > savefile.sam
parser.add_argument("->",help = "'*.sam','*.fasta','*.fastq'", required=True)
does not work as it gives: error: unrecognized arguments: -
can I do this with argparse or should I settle for -s?
> savefile.sam is shell syntax and means "send output to the file savefile.sam". Argparse won't even see this part of the command because the shell will interpret it first (assuming you issue this command from a suitable shell).
While your command does make sense, you shouldn't try to use argparse to implement it. Instead, if an -s isn't detected, simply send the script's output to stdout. You can achieve this by setting the default for -s:
parser.add_argument("-s",
type=argparse.FileType("w"),
help="'*.sam','*.fasta','*.fastq'",
default=sys.stdout)
This way, you can run python script.py > savefile.sam, and the following will happen:
The shell will evaluate python script.py.
argparse will see no additional arguments, and will use the default sys.stdout.
Your script will send output to stdout.
The shell will redirect the script's output from stdout to savefile.sam.
Of course, you can also send the stdout of the script into the stdin the another process using a pipe.
Note that, using FileType, it's also legal to use -s - to specify stdout. See here for details.
In a sense your argparse works
import argparse
import sys
print sys.argv
parser=argparse.ArgumentParser()
parser.add_argument('->')
print parser.parse_args('-> test'.split())
print parser.parse_args()
with no arguments, it just assigns None to the > attribute. Note though that you can't access this attribute as args.>. You'd have to use getattr(args,'>') (which is what argparse uses internally). Better yet, assign this argument a proper long name or dest.
1401:~/mypy$ python stack29233375.py
['stack29233375.py']
Namespace(>='test')
Namespace(>=None)
But if I give a -> test argument, the shell redirection consumes the >, as shown below:
1405:~/mypy$ python stack29233375.py -> test
usage: stack29233375.py [-h] [-> >]
stack29233375.py: error: unrecognized arguments: -
1408:~/mypy$ cat test
['stack29233375.py', '-']
Namespace(>='test')
Only the - passes through in argv, and on to the parser. So it complains about unrecognized arguments. An optional positional argument could have consumed this string, resulting in no errors.
Notice that I had to look at the test file to see the rest of the output - because the shell redirected stdout to test. The error message goes to stderr, so it doesn't get redirected.
You can change the prefix char from - (or in addition to it) with an ArgumentParser parameter. But you can't use such a character alone. The flag must be prefix + char (i.e. 2 characters).
Finally, since this argument is required, do you even need a flag? Just make it a positional argument. It's quite common for scripts to take input and/or output file names as positional arguments.
the arg include an action field and optional switches that modify the behavior of the actions.
the argparse code is like the below:
parser=argparse.ArgumentParser()
parser.add-argument('action',metavar='action', choices=['analysis','report','update'],nargs='?', default='report')
parser.add-argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser.add-argument('-v',dest='verbose',action='store-true',default=False)
parser.add-argument('-o',dest='offline',action='store-true',default=False)
parser.add-argument('-n',dest='names',required=False)
i want to make the switch option -o, -d, -v only available for action=report, while option -n only available for action=analysis.
i know there is a mutual group setting, but it is just set for the arguments not for the argument values!
btw, does argparse support combine options, like -vo...???
First: Yes, combined options like -vo are supported
Now to the action switching:
Adding a SubParser to your ArgumentParser is exactely what you want to do.
See Section 15.4.5.1. Sub-commands in the Documentation of argparse, everything is explained to some detail and with example there
EDIT:
I know refer to your comment below this post:
If you do not provide a subcommand parameter in the program call, the parser normally gives you a kind advice how to use the program and then exits with a "too few arguments error"
I rewrote your code to show this
test.py
import argparse
parser=argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='sub-command help')
parser_analysis = subparsers.add_parser('analysis', help='analysis help text')
parser_analysis.add_argument('-n',dest='names',required=False)
parser_report = subparsers.add_parser('report', help='report help text')
parser_report.add_argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser_report.add_argument('-v',dest='verbose',action='store_true',default=False)
parser_report.add_argument('-o',dest='offline',action='store_true',default=False)
parser_update = subparsers.add_parser('update', help='update help text')
parser.parse_args()
Now some calls of this test.py with different arguments:
$python test.py
usage: test.py [-h] {analysis,report,update} ...
test.py: error: too few arguments
$python test.py -h
usage: test.py [-h] {analysis,report,update} ...
positional arguments:
{analysis,report,update}
sub-command help
analysis analysis help text
report report help text
update update help text
optional arguments:
-h, --help show this help message and exit
$python test.py report -h
usage: test.py report [-h] [-d {1,2,3,4,5,6,7,8,9}] [-v] [-o]
optional arguments:
-h, --help show this help message and exit
-d {1,2,3,4,5,6,7,8,9}
-v
-o
So as I see it the only problem is that the program throws an error after calling python test.py without any subcommand. So I would do s.th. like this
try:
args=parser.parse_args()
except:
exit(0)
to avoid that the user sees that there was an unhandled error. You have then the same behaviour as i.e. the svn command.
If you want to handle this in the way that a default subcommand is executed, you whould have to do s.th. like mentioned in this post answer:
Argparse - How to Specify a Default Subcommand
import sys
#...your parser definitions
if (len(sys.argv) < 2):
args = parser.parse_args(['update'])
else:
args = parser.parse_args()
This would parse a command update if the argument list in sys.argv is smaller than 2. Why 2? Because the first argument in the argument list is always the program which you called, i.e. test.py
The question is, do you really want this behaviour? Because there is no need for calling test.py update if i can always call test.py, so users will probably get lazy and never use test.py update command. Also if you later want a different default behaviour like test.py starting an interactive mode, users which are by then used to calling test.py for updating will get confused or their scripts which use your program get broken.
I am using argparse.ArgumentParser() in my script, I would like to display the pydoc description of my script as part of the '--help' option of the argparse.
One possibly solution can be to use the formatter_class or the description attribute of ArgumentParser to configure the displaying of help. But in this case, we need to use the 'pydoc' command internally to fetch the description.
Do we have some other ways (possibly elegant) to do it?
You can retrieve the docstring of your script from the __doc__ global. To add it to your script's help, you can set the description argument of the parser.
"""My python script
Script to process a file
"""
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('foo', help="Name of file to process")
p.parse_args()
Then the help will look like:
$ python tmp.py --help
usage: tmp.py [-h] foo
My python script
Script to process a file
positional arguments:
foo Name of file to process
optional arguments:
-h, --help show this help message and exit
You can use the epilog keyword argument instead of description to move the docstring to the end of the help, instead of immediately following the usage string.
There is an elegant argparse wrapper allowing to use a Python function docstring as a command help in your command line interface: dsargparse
It does this smartly keeping only the description part of the function docstring not the arguments part that can be irrelevant to your command.
As mentioned in its Readme:
dsargparse is a wrapper of argparse library which prepares helps and descriptions from docstrings. It also sets up functions to be run for each sub command, and provides a helper function which parses args and run a selected command.
Using this library, you don't need to write same texts in docstrings, help, and description.
Python optparse works very good when script usage is something like this
%prog [options] [args]
But I need to write help for script with 1 required argument, so usage will be like this
%prog action [options] [args]
You can see something similar when you use Subversion - its usage string is
svn <subcommand> [options] [args]
So my question is: is it possible to prepare help for required argument with optparse in the manner of Subversion? As a result I want to see help like this:
Usage: python myscript.py action [options] [args]
Available actions:
foo
bar
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-v, --verbose Verbose mode. Output debug log to stdout.
I think a good solution for you is argparse, which has been proposed for inclusion in Python 2.7 and 3.2. It handles subcommands, I believe as you want, and the linked page includes a link to a page on porting your code from optparse.
See also the question command-line-arguments-in-python, into which someone edited a list of references that appears to include exactly the same thing you want:
Yes. You can set the usage string like this:
usage = "%prog action [options] [args]"
parser = OptionParser(usage=usage)
parser.add_option("-v", "--verbose",
action="store_true", dest="verbose", default=True,
help="make lots of noise [default]")
Prints the following:
Usage: action [options] [args]
Options:
-h, --help show this help message and exit
-v, --verbose make lots of noise [default]
This was copied almost verbatim from the docs.
Edit:
Based on your comment you could use the description to achieve something similar, though you can't put new-line characters in it.
parser.description = 'Available actions: foo, bar'
Will look like this:
Usage: action [options] [args]
Available actions: foo, bar
Options:
-h, --help show this help message and exit
-v, --verbose make lots of noise [default]
I've run into this problem as well. My solution was to declare commands in a list or tuple, format them into the usage parameter of OptionParser and then use the args list provided by the parser to determine if a command was provided or not, since it technically has to be args[0]. Eg:
self.commands = ('foo', 'bar' ...)
self.parser = <initialized instance of OptionParser>
(self.options, self.args) = parser.parse_args()
if len(self.args) == 0:
self.parser.error("Command required")
self.command = self.args[0]
if not self.command in self.commands:
self.parser.error("Command not recognized")
#... etc
This kinda gets you a command system that looks like Subversion's, but admittedly optparse could be better. I've heard the argparse module is supposed to make it into the stdlib, but with 2.7 being the last of the 2 series releases, I guess you'd have to wait for it to be incorporated into 3.x. Of course you can just install argparse, but that's a drag in some cases.