python argparse don't show correct help message - python

I have a python script using argparse. After typing in python script_name.py -h on the command line, it shows help message for another command but the code still works. The script can recognize options defined in it and run well. It looks like the script is packaged by something. I put argparse in a function and everything works well at the beginning. I just can't find out what causes the help message changed.
Here is the code:
#!/usr/bin/env python
import os
import sys
import json
import logging
import argparse
import handlers
HZZ_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(os.path.dirname(HZZ_DIR))
logger = logging.getLogger('hzz_logger')
logger.setLevel(logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(console)
def parse_args():
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('job', choices=['ws','lm','np'],
help="ws: workspace; lm: limit; np: npranking")
arg_parser.add_argument('-a', '--action', nargs=1,
help="for Limit and NPranking: get/plot (limit/pull)")
arg_parser.add_argument('-b', '--blinded', action='store_true',
help="for Limit: true -- do expected only, false -- do observed as well.")
arg_parser.add_argument('-v', '--version', nargs=1, type=int,
help="input version")
arg_parser.add_argument('-t', '--tag', nargs=1,
help='workspace tag')
arg_parser.add_argument('-m', '--mass', nargs='+', type=int,
help='signal mass(es)')
arg_parser.add_argument('-c', '--config', nargs=1,
help='configure file')
arg_parser.add_argument('-u', '--update', action='store_true',
help="update default settings")
args = arg_parser.parse_args()
return args
def load_settings(args):
pass
def run_job(settings):
pass
def execute():
args = parse_args()
settings = load_settings(args)
run_job(settings)
if __name__ == '__main__':
execute()
The help message is pasted here, which is actually the help message a command not directly used in this code. The options for this command can also be recognized...
$ python hzz_handler.py -h
Usage: python [-l] [-b] [-n] [-q] [dir] [[file:]data.root] [file1.C ... fileN.C]
Options:
-b : run in batch mode without graphics
-x : exit on exception
-n : do not execute logon and logoff macros as specified in .rootrc
-q : exit after processing command line macro files
-l : do not show splash screen
dir : if dir is a valid directory cd to it before executing
-? : print usage
-h : print usage
--help : print usage
-config : print ./configure options
-memstat : run with memory usage monitoring

Wow, another anti-Pythonic ROOT mystery! Your question and the comments are really helpful. Why did not anybody post the answer with ROOT.PyConfig.IgnoreCommandLineOptions = True?
Here is a primitive work-around:
import argparse
# notice! ROOT takes over argv and prints its own help message when called from command line!
# instead I want the help message for my script
# therefore, check first if you are running from the command line
# and setup the argparser before ROOT cuts in
if __name__ == '__main__':
parser = argparse.ArgumentParser(
formatter_class = argparse.RawDescriptionHelpFormatter,
description = "my script",
epilog = "Example:\n$ python my_script.py -h"
)
parser.add_argument("param", type=str, help="a parameter")
parser.add_argument("-d", "--debug", action='store_true', help="DEBUG level of logging")
args = parser.parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
logging.debug("parsed args: %s" % repr(args))
import ROOT
...
if __name__ == '__main__':
<do something with args>

In sort the answer is that always calls import ROOT after the argparse. Then ROOT won't take over the argparse and prints the required message that we want.

Related

Allowing Python Script to run even on --help argument in argparse

I am using Argparse module in python for developing a Command Line Tool.
Here's the parser code:
from argparse import ArgumentParser
def arguments():
parser = ArgumentParser()
parser.add_argument('-c' , '--comms' , action = "store" , default = None , type = str , dest = "command",
help = 'Choosing a Command')
parser.add_argument( '-s' , '--search' , action = 'store' , default = None , type = str , dest = 'search_path' ,
help = 'Search for a command' )
parser.add_argument( '-f' , '--config' , action = 'store_true' ,
help = 'Show the present configuration')
parser.add_argument('--flush_details' , action = "store_false" ,
help = "Flushes the current commands buffer")
return parser.parse_args()
def main():
parser_results = arguments()
#More code comes here to analyze the results
However, when I run the code python foo.py --help, it never runs the script post parsing the arguments. Is there anything I can do to stop the behaviour. I want to analyse the parser results even if it is just asked for --help switch.
Would like to know what can I do to continue the script even after --help has been used
Remark: you should not do that, because it does not respect established usages and may disturb users. For the remaining of the answer I shall assume that you are aware of it and have serious reasons for not respecting common usages.
The only way I can imaging is to remove the standard -h|--help processing and install your own:
parser = ArgumentParser(add_help=False)
parser.add_argument('-h' , '--help', help = 'show this help', action='store_true')
...
Then in option processing, you just add:
parser_results = parser.parse_args()
if parser_results.help:
parser.print_help()
As user Thierry Lathuille has said in the comments, --help is meant to print the help and exit.
If for some reason you want to print the help and run the script, you can add your own argument like so:
import argparse
parser = argparse.ArgumentParser(description="This script prints Hello World!")
parser.add_argument("-rh", "--runhelp", action="store_true", help="Print help and run function")
if __name__ == "__main__":
args = parser.parse_args()
if args.runhelp:
parser.print_help()
print('Hello World!')
If the name of the script is main.py:
>>> python main.py -rh
usage: main.py [-h] [-rh]
This script prints Hello World!
optional arguments:
-h, --help show this help message and exit
-rh, --runhelp Print help and run function
Hello World!
EDIT:
If you insist on using --help instead of a custom argument:
import argparse
parser = argparse.ArgumentParser(description="This script prints Hello World!", add_help=False)
parser.add_argument("-h", "--help", action="store_true", help="Print help and run function")
if __name__ == "__main__":
args = parser.parse_args()
if args.help:
parser.print_help()
print('Hello World!')
If the name of the script is main.py:
>>> python main.py -h
usage: main.py [-h]
This script prints Hello World!
optional arguments:
-h, --help Print help and run function
Hello World!
Set the add_help parameter for argparse.ArgumentParser to False to disable -h and --help:
parser=argparse.ArgumentParser(add_help=False)
Then, add --help:
parser.add_argument('--help',action='store_true')

How to make python argcomplete run with PowerShell

I am trying to see if I can use argument autocompletion in Windows Powershell for my python script. Powershell allegedly supports argument completion.
Here's a minimal example which does not work:
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argparse
import argcomplete
from argcomplete.completers import EnvironCompleter
def argument_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Minimal app where arcomplete should work")
parser.add_argument("--version", action="store_true", help="print out version").completer = EnvironCompleter
parser.add_argument("--do-this", action="store_true", help="do this").completer = EnvironCompleter
parser.add_argument("--do-that", action="store_true", help="do that").completer = EnvironCompleter
return parser
if __name__ == "__main__":
parser = argument_parser()
argcomplete.autocomplete(parser)
cli_args = parser.parse_args()
Then in Powershell I try: to type python -i minimal - and then press <TAB>, Nothing happens. What am I doing wrong? Maybe I should mention that I did not enable global autocompletion. Somehow when I run activate-global-python-argcomplete in powershell, I get an "Open With ..." dialogue.
It is possible, you just need a wrapper script and mind the correct encoding
parser = argument_parser()
output_stream = None
if "_ARGCOMPLETE_POWERSHELL" in os.environ:
output_stream = codecs.getwriter("utf-8")(sys.stdout.buffer)
argcomplete.autocomplete(parser, output_stream=output_stream)
args = parser.parse_args()
I have composed a minimal working example. It's based on Tibor's mat example, but that one is actually not working because it is missing the utf-8 encoding of the buffer.

argparse conflict when used with two connected python3 scripts

I am trying to run one python script (Main_Script) which is supposed to get argparse flag and this script at the same time calls another script(Sub_Script) which is also supposed to get the flag to input. And when I call Main_Script I get an error which says that I can't use the flag because it is not defined in the script but it is actually defined. The error notification makes me use the flag from subscript instead.
MAIN_SCRIPT
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('-p', '--print_positive_results', action='store_true')
args = parser.parse_args()
PRINT_POSITIVE = args.print_positive_results
#I then use rhi global variable PRINT_POSITIVE
SUB_SCRIPT
import argparse
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('-d', '--debug', action='store_true')
args = parser.parse_args()
And when I call python MAIN_SCRIPT.py -p I get this
usage: test_grammar.py [-h] [-d]
test_grammar.py: error: unrecognized arguments: -p
DEBUG = False
if (args.debug ):
DEBUG = True
Seems like the command line args from the main script are passed through to the sub script.
You could try to (and probably should) wrap the argparse stuff into:
if __name__ == '__main__':
<argparse stuff>
With this the code is only called when the script is called from the command line. The real code could be outsourced into a function. This way you can use the script from command line with argparse and only import the function from the script if you call it from another script:
Main script:
import argparse
import subscript
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('-p', '--print_positive_results', action='store_true')
args = parser.parse_args()
...
subscript.your_function(<whatever your args are>)
...
Sub script:
import argparse
def your_function(<your args>):
<your code>
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('-d', '--debug', action='store_true')
args = parser.parse_args()
your_function(<whatever your args are>)
...

Parse commandline args in unittest python

I am creating a test case in python using unittest module.
I did create a parsing argument list that i want to get from user.
But when i use that argument while executing the python script, it gives error: "option -i not recognized
Usage: testing.py [options] [test] [...]"
code snippet:
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
ns, args = parser.parse_known_args(namespace=unittest)
#args = parser.parse_args()
return ns, sys.argv[:1] + args
if __name__ == '__main__':
unittest.main()
The error m getting on executing the above script with -i somefile.txt is:
option -i not recognized
Usage: testing.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
Examples:
testing.py - run default set of tests
testing.py MyTestSuite - run suite 'MyTestSuite'
testing.py MyTestCase.testSomething - run MyTestCase.testSomething
testing.py MyTestCase - run all 'test*' test methods
in MyTestCase
Any help would be appreciated.
This script captures the -i command, while still allowing unittest.main to do its own commandline parsing:
import unittest
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
ns, args = parser.parse_known_args(namespace=unittest)
#args = parser.parse_args()
return ns, sys.argv[:1] + args
if __name__ == '__main__':
import sys
args, argv = parse_args() # run this first
print(args, argv)
sys.argv[:] = argv # create cleans argv for main()
unittest.main()
produces:
1113:~/mypy$ python stack44236745.py -i testname -v
(<module 'unittest' from '/usr/lib/python2.7/unittest/__init__.pyc'>, ['stack44236745.py', '-v'])
Hello Class
test_addnum (__main__.Testclass) ... Execute the test case
simple_value = testname
ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
It looks rather kludgy, but does seem to work.
The idea is to run your own parser first, capturing the -i input, and putting the rest back into sys.argv. Your definition of parse_args suggests that you are already trying to do that.
Thanks hpaulj, your solution really helped me and I found one more solution for this problem. Hope it helps someone else facing the same issue.
import unittest
import argparse
import sys
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
sys.argv[1:] = args.unittest_args
unittest.main()
Now executing the script with option -i as python testing.py -i somefile.txt gives result as
Hello Class
Execute the test case
simple_value = somefile.txt
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Your code is setting up your argument parser using
argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
in a method of your test class. But I see no indication that the code is actually calling the method.
So at the time you start the program, the parser does not yet exist, because the method TestClass.parse_args() hasn't been called yet.
Move the creation of the parser and specification of its parameters out of the class so that the code calls it when the program starts.
You are running unittest.main() as the main program. So it's complaining rightfully that it does not know about the option you wrote:
"option -i not recognized"
If you want to create your own test suite launcher you should look into
TextTestRunner

Pass a directory with argparse (no type checking needed)

I've written a file crawler and I'm trying to expand it. I want to use argparse to handle settings for the script including passing the starting directory in at the command line.
Example: /var/some/directory/
I have gotten several other arguments to work but I'm unable to pass this directory in correctly. I don't care if it's preceded by a flag or not, (e.g -d /path/to/start/) but I need to make sure that at the very least, this is argument is used as it will be the only mandatory option for the script to run.
Code Sample:
parser = argparse.ArgumentParser(description='py pub crawler...')
parser.add_argument('-v', '--verbose', help='verbose output from crawler', action="store_true")
parser.add_argument('-d', '--dump', help='dumps and replaces existing dictionaries', action="store_true")
parser.add_argument('-f', '--fake', help='crawl only, nothing stored to DB', action="store_true")
args = parser.parse_args()
if args.verbose:
verbose = True
if args.dump:
dump = True
if args.fake:
fake = True
Simply add:
parser.add_argument('directory',help='directory to use',action='store')
before your args = parser.parse_args() line. A simple test from the commandline shows that it does the right thing (printing args at the end of the script):
$ python test.py /foo/bar/baz
Namespace(directory='/foo/bar/baz', dump=False, fake=False, verbose=False)
$ python test.py
usage: test.py [-h] [-v] [-d] [-f] directory
test.py: error: too few arguments

Categories

Resources