argparse subparser exit_on_error - python

I want to disable exit_on_error for a parser and subparser, to prevent error messages:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(exit_on_error=False)
subparsers = parser.add_subparsers()
subcommand = subparsers.add_parser('subcommand')
subcommand.add_argument('argument')
try:
arguments = parser.parse_args()
except:
parser.print_usage(std.stderr)
With no arguments (ie: no subparser invoked) it works as intended:
$ python3 main.py
usage: main.py {subcommand} ...
But when I use a subcommand, it doesn't:
$ python3 main.py subcommand
usage: main.py subcommand [-h] argument
main.py subcommand: error: the following arguments are required: argument
usage: main.py {subcommand} ...
In this instance, I'd want it to print this instead:
$ python3 main.py subcommand
usage: main.py {subcommand} ...
I checked, and neither add_subparsers nor add_parser take an exit_on_error argument. How can I suppress printing those error messages?

Related

Calling main with sys.argv argparse

I have a main function which takes args from sys.argv using argparse:
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-arg1', '--argument1', type=str, required=True)
parser.add_argument('-arg2', '--argument2', type=str, required=True)
args = parser.parse_args()
The main function is called from another module as mod.main().
When i print what is passed to the main function the args are: ['-arg1', 'val1', '-arg2', 'val2'] which is the correct list of arguments.
However, when i run it it seems that the args are not passed correctly with argparse.
['-arg1', 'val1', '-arg2', 'val2']
usage: -arg1 [-h] -arg2 ARG2 -arg1 ARG1
-arg1: error: the following arguments are required: -arg1
The issue is that i am not running the run_module.py which calls the module_to_call.main() from the command line but passing it as args to spark-submit in order to run it on EMR.
"HadoopJarStep": {
"Args": [
"spark-submit",..., "run_module.py", "module_to_call", "-arg1", "val1", "-arg2", "val2",...
Here is a minimal example for you
#!/usr/bin/env python
# argparse_example.py
import sys
import argparse
def main():
print(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument('-arg1', '--argument1', type=str, required=True)
parser.add_argument('-arg2', '--argument2', type=str, required=True)
args = parser.parse_args()
print(args)
if __name__ == "__main__":
main()
You can run it with
python argparse_example.py -arg1 val1 -arg2 val2
And get
['argparse_example.py', '-arg1', 'val1', '-arg2', 'val2']
Namespace(argument1='val1', argument2='val2')
Or run it without arguments
python argparse_example.py
to get the help message:
['argg.py']
usage: argg.py [-h] -arg1 ARGUMENT1 -arg2 ARGUMENT2
argg.py: error: the following arguments are required: -arg1/--argument1, -arg2/--argument2
UPDATE
From comments by the OP I realize that sys.argv got mangled on the way to function main() by some enveloping code and the first argument got cut off.
The argument list ['-arg1', 'val1', '-arg2', 'val2'] is not the correct list of arguments as it is missing sys.argv[0] — the name of the python script being called from the command line. As argparse processes sys.argv[1:], it is unable to find '-arg1' and therefore fails with an error.
In this case you can add a dummy element to sys.argv to make sure that -arg1 will be passed to argparse when your code is being run as a module.
#!/usr/bin/env python
# argparse_example.py
import sys
import argparse
def main():
print(sys.argv)
if __name__ != "__main__":
# we are running as a module so add a dummy argument
sys.argv = ['argparse_example.py'] + sys.argv
parser = argparse.ArgumentParser()
parser.add_argument('-arg1', '--argument1', type=str, required=True)
parser.add_argument('-arg2', '--argument2', type=str, required=True)
args = parser.parse_args()
print(args)
if __name__ == "__main__":
main()
You can test it with the following enveloping code
#!/usr/bin/env python
# runner.py
import sys
import argparse_example
if __name__ == "__main__":
sys.argv = sys.argv[1:]
argparse_example.main()
And call it from the command line like this:
python runner.py -arg1 val1 -arg2 val2
The result should be the same as when calling argparse_example directly from the command line, even though sys.argv will be missing the first element:
['-arg1', 'val1', '-arg2', 'val2']
Namespace(argument1='val1', argument2='val2')

Argparse: print arguments with non-default value

In the argparse module, is it possible (easily) to print/log arguments that have been changed from default values when running a script? For example,
my_script.py
argparse.add_argument("--arg1", default="val1")
argparse.add_argument("--arg2", default="val2")
Running:
python my_script.py --arg2 newval2
Print:
Arguments changed:
arg2 : newval2
Once you have parsed your arguments you can, take a look to this script:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-c", dest="c", default=1)
def main():
options = parser.parse_args()
print(options.c)
if __name__ == "__main__":
main()
If you save this code in a script tmp.py and then call:
$>python3 tmp.py
$>1
$>python3 tmp.py -c 12
$>12

Passing command line arguments in python by pytest

I am able to pass command line arguments when running
python <filename>.py arg1
But when am trying to pass the command line arguments for running pytest it fails and gives error as below. Can you please advise.
pytest <filename>.py arg1
ERROR: file not found: arg1
EDIT:
For example am thinking of using it this way assuming I have passed an argument and am reading it via sys.argv:
import sys
arg = sys.argv[3]
def f():
return 3
def test_function():
assert f() == arg
Your pytest <filename>.py arg1 command is trying to call pytest on two modules <filename>.py and arg1 , But there is no module arg1.
If you want to pass some argument before running pytest then run the pytest from a python script after extracting your variable.
As others suggested though you would probably want to parameterize your tests in some other way, Try:Parameterized pytest.
# run.py
import pytest
import sys
def main():
# extract your arg here
print('Extracted arg is ==> %s' % sys.argv[2])
pytest.main([sys.argv[1]])
if __name__ == '__main__':
main()
call this using python run.py filename.py arg1
Here's the method I just cooked up from reading the parameterized pytest docs and hacking for a while... I don't know how stable or good it is going to be overall since I just got it working.
I did however check that HTML coverage generation works with this method.
add a file to your test directory for configuring the command-line args you want to pass:
tests\conftest.py
# this is just so we can pass --server and --port from the pytest command-line
def pytest_addoption(parser):
''' attaches optional cmd-line args to the pytest machinery '''
parser.addoption("--server", action="append", default=[], help="real server hostname/IP")
parser.addoption("--port", action="append", default=[], help="real server port number")
and then add a test file, with this special pytest_generate_tests function which is called when collecting a test function
tests\test_junk.py
def pytest_generate_tests(metafunc):
''' just to attach the cmd-line args to a test-class that needs them '''
server_from_cmd_line = metafunc.config.getoption("server")
port_from_cmd_line = metafunc.config.getoption("port")
print('command line passed for --server ({})'.format(server_from_cmd_line))
print('command line passed for --port ({})'.format(port_from_cmd_line))
# check if this function is in a test-class that needs the cmd-line args
if server_from_cmd_line and port_from_cmd_line and hasattr(metafunc.cls, 'real_server'):
# now set the cmd-line args to the test class
metafunc.cls.real_server = server_from_cmd_line[0]
metafunc.cls.real_port = int(port_from_cmd_line[0])
class TestServerCode(object):
''' test-class that might benefit from optional cmd-line args '''
real_server=None
real_port = None
def test_valid_string(self):
assert self.real_server!=None
assert self.real_port!=None
def test_other(self):
from mypackage import my_server_code
if self.real_server != None:
assert "couldn\'t find host" not in my_server_code.version(self.real_server, self.real_port)
then run (with HTML coverage, for example) with:
pytest tests\test_junk.py --server="abc" --port=123 --cov-report html --cov=mypackage
It seems monkeypatch also works.
Example:
import sys
def test_example(monkeypatch):
monkeypatch.setattr(sys, 'argv', ['/path/to/binary', 'opt1', '...'])
assert f() == '...'
def test_another():
# sys.argv is not modified here
assert f() != '...'

Call a python script within another python script causes problems with argparse

After trying the solution offered by this thread:
What is the best way to call a Python script from another Python script?
I came along with another problem dealing with arguments.
I have:
test1.py
def some_func():
print 'in test 1, unproductive'
if __name__ == '__main__':
# test1.py executed as script
# do something
some_func()
service.py (with dummy arguments)
import argparse
import test1
actions = ['start', 'remove']
parser = argparse.ArgumentParser()
parser.add_argument("action", help="Possible actions are: "
"'{d[0]}|{d[1]}' ".format(d=actions))
parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
args = parser.parse_args()
def service_func():
print 'service func'
if __name__ == '__main__':
# service.py executed as script
# do something
service_func()
test1.some_func()
This works:
python service.py start
service func
in test 1, unproductive
However, if I want to add arguments in test1.py as well:
test1.py
import argparse
######## new stuff ##########
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
args = parser.parse_args()
##############################
def some_func():
print 'in test 1, unproductive'
if __name__ == '__main__':
# test1.py executed as script
# do something
some_func()
Now I get:
python service.py start
usage: service.py [-h] [-d]
service.py: error: unrecognized arguments: start
Not sure why...
python service.py start - to argparse 'start' looks like a positional argument. It does not have a '--' to mark it as a flag string.
I'm guessing the parser in service.py is handling it ok, though you don't display or otherwise use the resulting args.
The usage in the error message is consistent with the parser in test1.py. It does not define a positional, just the --debug
usage: service.py [-h] [-d]
test1.py is using the same sys.argv list.
There are several solutions:
add a positional argument to test1
use parse_known_args instead of parse_args.
modify sys.argv in service.py (after parsing) to remove this 'start' string. A change in sys.argv in this script will carry over to test1. Print sys.argv in both to be sure.
use REMAINDER as documented in the docs
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities:
Call
args, extras = parser.parse_known_args()
This would set
args to Namespace(debug=False) and extras to ['start']. Otherwise you get a tuple of these two values, (Namespace(debug=False), ['start']).
If you haven't modified sys.argv in the first script, the 2nd one will see the same list. Parsing does not modify this list.

Python: run a unittest.TestCase without calling unittest.main()?

I've written a small test suite in Python's unittest:
class TestRepos(unittest.TestCase):
#classmethod
def setUpClass(cls):
"""Get repo lists from the svn server."""
...
def test_repo_list_not_empty(self):
"""Assert the the repo list is not empty"""
self.assertTrue(len(TestRepoLists.all_repos)>0)
def test_include_list_not_empty(self):
"""Assert the the include list is not empty"""
self.assertTrue(len(TestRepoLists.svn_dirs)>0)
...
if __name__ == '__main__':
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='tests',
descriptions=True))
The output is formatted as Junit test using the xmlrunner pacakge.
I've added a command line argument for toggling JUnit output:
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Validate repo lists.')
parser.add_argument('--junit', action='store_true')
args=parser.parse_args()
print args
if (args.junit):
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='tests',
descriptions=True))
else:
unittest.main(TestRepoLists)
The problem is that running the script without --junit works, but calling it with --junit clashes with unittest's arguments:
option --junit not recognized
Usage: test_lists_of_repos_to_branch.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
...
How can I run a unittest.TestCase without calling unittest.main()?
You really should use a proper test runner (such as nose or zope.testing). In your specific case, I'd use argparser.parse_known_args() instead:
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--junit', action='store_true')
options, args = parser.parse_known_args()
testrunner = None
if (options.junit):
testrunner = xmlrunner.XMLTestRunner(output='tests', descriptions=True)
unittest.main(testRunner=testrunner, argv=sys.argv[:1] + args)
Note that I removed --help from your argument parser, so the --junit option becomes hidden, but it will no longer interfere with unittest.main. I also pass the remaining arguments on to unittest.main().

Categories