How to pass an argument calling a function using ArgParse? - python

I'm trying to call function_3 with an argument but i'm receiving a error unrecognized arguments. I'm calling this way: python script.py --pass test
import argparse
import sys
def function_1():
print('Function 1')
def function_2():
print('Function 2')
def function_3(arg):
print(f'Function 3 {arg}')
if __name__ == "__main__":
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_f1 = subparsers.add_parser('fc1', help='Function 1')
parser_f1.set_defaults(func=function_1)
parser_f2 = subparsers.add_parser('fc2', help='Function 2')
parser_f2.set_defaults(func=function_2)
parser_f3 = subparsers.add_parser('fc3', help='Function 3')
parser_f3.add_argument("pass", help="function 3 argument")
parser_f3.set_defaults(func=function_3)
if len(sys.argv) <= 1:
sys.argv.append('--help')
options = parser.parse_args()
options.func()
Error
usage: argtest.py [-h] {fc1,fc2,fc3} ...
argtest.py: error: unrecognized arguments: --arg

First, your pass option is only available to the fc3 subparser, so rather than python script.py --pass test, you would want:
python script.py fc3 --pass test
But you've defined a positional argument, not a command line option. You need to either call your script like this:
python script.py fc3 test
Or you need to fix your code:
parser_f3.add_argument('--pass', help='function 3 argument')
That would allow you run python script.py fc3 --pass test.
There are still additional problems with the code; your function3 function requires an argument, but you're calling options.func() with no arguments, which will result in a TypeError exception.

I don't suggest doing it this way, I'd personally suggest just using click instead, but this is one method for doing it I guess...
import sys
from argparse import ArgumentParser
def parse_args():
parser = ArgumentParser()
parser.add_argument(
"-f", "--function", type=str, help="name of the function to call", default="echo"
)
parser.add_argument("func_args", nargs="*")
return parser.parse_args(args=None if sys.argv[1:] else ["--help"])
def echo(*args):
print(' '.join(*args))
if __name__ == "__main__":
args = parse_args()
eval(f"{args.function}({args.func_args})")
This approach feels fragile and wrong to me, but I believe this is line with the behavior you're asking for.

Related

python argparse option string not complete but value is assigned

Argparse doesn't seem to check for entire string of option to assign the value. Is this a bug or intended one? What are the use cases for this if this is intended?
Run the following program using python3 test.py -test "testing"
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-testurl', dest='testurl')
args = parser.parse_args()
print(args)
Output is
Namespace(testurl='testing')
We can use double dash (--) in options and make allow_abbrev=False in parser to force the exact argument to be parsed [reference: argument parser documentation]:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument('--testurl', dest='testurl')
args = parser.parse_args()
print(args)
Output:
> python3 df_reg.py --test "testing"
usage: df_reg.py [-h] [--testurl TESTURL]
df_reg.py: error: unrecognized arguments: --test testing
> python3 df_reg.py --testurl "testing"
Namespace(testurl='testing')
References:
Argument parser documentation

Unittest has unrecognized arguments

I'm running unit tests for my program.py file. I found an error: python3 -m unittest: error: unrecognized arguments:.
I believe the error comes from the argparse library I'm using where the target code is expecting some arguments.
Target file: program.py:
import argparse
parse = argparse.ArgumentParser(description="Command line program.")
parse.add_argument("--key", type=str,
help="Enter key")
parse.add_argument("--output", type=str,
help="Path to place results.")
args = parse.parse_args()
def program():
# Use args here
def writefile():
# Uses args and write to file
if __name__ == "__main__":
program()
Test file: program_test.py:
import unittest
import program
class TestProgram(unittest.TestCase):
def setUp(self):
self.argv_list = ["--key", "somefile.txt",
"--output", "myresultfile.txt"]
def test_program_stuff(self):
# See "Things I've tried"
program.writefile(...)
Command:
me#mylinux:myprogram$ env/bin/python3 -m unittest -v program_test.py
usage: python3 -m unittest [-h] [--key KEY] [--output OUTPUT]
python3 -m unittest: error: unrecognized arguments: -v program_test.py
Things I've tried:
Mock the argparse.Namespace with argparse.Namespace(key="key.txt", output="result.txt")
Manipulate sys.args in test_program_stuff by sys.args.append(self.argv_list)
I've looked at solutions to unit testing argparse but none have helped so I'm thinking it may not be the same issue:
Python argparse "unrecognized arguments" error
argparse fails when called from unittest test
argparse and unittest python
Note: I do realize that this is a duplicate of Pytest unrecognized arguments when importing file with argparse, however, that question was unfortunately not answered. It is also a duplicate of How to call function with argparse arguments in unittests?, however, he doesn't want to provide arguments and instead wants to call another function defined in the module.
Essentially, the problem can be reduced to the following:
# main.py
import argparse
parse = argparse.ArgumentParser()
parse.add_argument("--foo", action="store_true")
args = parse.parse_args()
and
# tests.py
import main
If we run that with python -m unittest ./tests.py, we receive the following output:
usage: python -m unittest [-h] [--foo FOO]
python -m unittest: error: unrecognized arguments: ./tests.py
The problem is that if you import something, all the top level code will run during the import. Usually, this isn't a problem because in a library you only really define functions to be used by other programs, however, in your case the parse.parse_args() runs.
This can be resolved by guarding this logic similar to what you already did:
import argparse
def main():
parse = argparse.ArgumentParser()
parse.add_argument("--foo")
args = parse.parse_args()
if __name__ == "__main__":
main()
Here, __name__ will contain the name of the module which would be "main" if it is imported or "__main__" if it is run directly. Therefore, the main() function will not be called during the unit test. Read more about this here.
However, in your specific case, it seems that you want to test the functionality of the main() function (or rather the code that you have in your main module.)
There are generally two ways to achieve this:
You can simply accept the arguments in main():
import argparse
import sys
def main(argv):
parse = argparse.ArgumentParser()
parse.add_argument("--foo", action="store_true")
args = parse.parse_args(argv[1:])
if __name__ == "__main__":
main(sys.argv)
Then you are able to provide these arguments in the unit test as well:
import unittest
import main
class Tests(unittest.TestCase):
def test_whatever(self):
main.main(["main.py", "--foo"])
You can use a mock-framework to change the value of sys.argv. In this situation would seem a bit over-engineered. However, if you are interested, that is answered here.
The parse.parse_args is outside of a method so will get run when the file is imported by the unittest file. This is why it is complaining that it doesn't have the expected arguments.
A solution is to move the parse_args into a function. e.g:
import argparse
import sys
def parse_args(sys_args):
parse = argparse.ArgumentParser(description="Command line program.")
parse.add_argument("--key", type=str,
help="Enter key")
parse.add_argument("--output", type=str,
help="Path to place results.")
return parse.parse_args(sys_args)
def program(key, output):
# Use args here
use_key(key)
writefile(output)
def use_key(key):
print(f"I have the key: {key}")
def writefile(filename):
# Uses args and write to file
print(f"I will write to file: {filename}")
if __name__ == "__main__":
parsed_args = parse_args(sys.argv[1:])
program(parsed_args.key, parsed_args.output)
This then allows things to test individually. e.g:
import io
import unittest
from unittest import mock
import program
class TestProgram(unittest.TestCase):
def setUp(self):
self.argv_list = ["--key", "somefile.txt",
"--output", "myresultfile.txt"]
def test_program_parse(self):
# See "Things I've tried"
args = program.parse_args(self.argv_list)
self.assertEqual("somefile.txt", args.key)
self.assertEqual("myresultfile.txt", args.output)
def test_program(self):
with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
program.use_key('val1')
self.assertEqual('I have the key: val1\n', fake_out.getvalue())
def test_writefile(self):
with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
program.writefile('junk.txt')
self.assertEqual('I will write to file: junk.txt\n', fake_out.getvalue())
if __name__ == '__main__':
unittest.main(verbosity=2)

Python ArgParse add help message when subparsers are omitted [duplicate]

With python's argparse, how do I make a subcommand a required argument? I want to do this because I want argparse to error out if a subcommand is not specified. I override the error method to print help instead. I have 3-deep nested subcommands, so it's not a matter of simply handling zero arguments at the top level.
In the following example, if this is called like so, I get:
$./simple.py
$
What I want it to do instead is for argparse to complain that the required subcommand was not specified:
import argparse
class MyArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.print_help(sys.stderr)
self.exit(0, '%s: error: %s\n' % (self.prog, message))
def main():
parser = MyArgumentParser(description='Simple example')
subs = parser.add_subparsers()
sub_one = subs.add_parser('one', help='does something')
sub_two = subs.add_parser('two', help='does something else')
parser.parse_args()
if __name__ == '__main__':
main()
There was a change in 3.3 in the error message for required arguments, and subcommands got lost in the dust.
http://bugs.python.org/issue9253#msg186387
There I suggest this work around, setting the required attribute after the subparsers is defined.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
update
A related pull-request: https://github.com/python/cpython/pull/3027
In addition to hpaulj's answer: you can also use the required keyword argument with ArgumentParser.add_subparsers() since Python 3.7. You also need to pass dest as argument. Otherwise you will get an error: TypeError: sequence item 0: expected str instance, NoneType found.
Example file example.py:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=True)
foo_parser = subparsers.add_parser("foo", help="command foo")
args = parser.parse_args()
Output of the call without an argument:
$ python example.py
usage: example.py [-h] {foo} ...
example.py: error: the following arguments are required: command
How about using required=True? More info here.
You can use the dest argument, which is documented in the last example in the documentation for add_subparsers():
# required_subparser.py
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
one = subparsers.add_parser('one')
two = subparsers.add_parser('two')
args = parser.parse_args()
Running with Python 2.7:
$python required_subparser.py
usage: required_subparser.py [-h] {one,two} ...
required_subparser.py: error: too few arguments
$python required_subparser.py one
$# no error

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>)
...

How to print a usage statement when no arguments are passed?

I am passing a single, positional argument string called FILE, but when no arguments are passed, I want it to print a usage statement.
Every time I write './files.py' in my command-line with no arguments after it, my code does nothing. What am I doing wrong?
import argparse
import re
#--------------------------------------------------
def get_args():
"""get arguments"""
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('FILE', help='Pass a file', type=str)
return parser.parse_args()
#--------------------------------------------------
def main():
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
# --------------------------------------------------
if __name__ == '__main__':
main()
Expected outcome:
$ ./files.py
Usage: files.py FILE
What I am getting:
$./files.py
$
You never run main...
import argparse
import re
#--------------------------------------------------
def get_args():
"""get arguments"""
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('FILE', help='Pass a file', type=str)
return parser.parse_args()
#--------------------------------------------------
def main():
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
main()
You need to define the entry point of your code. If you want to call this as you are describing (./files.py) you need to define the main entry point like this:
if __name__ == "__main__":
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
You have to tell your operating system that the script must be executed by Python. Add a shebang as the first line of your script:
#!/usr/bin/env python3
import argparse
...
Otherwise, you have to explicitly execute the script with Python:
python3 ./files.py
You must call your main function. A good place is at the end of the script, guarded to be run on execution only:
if __name__ == '__main__': # do not run on import
main()
This gives the desired output:
$ python3 so_script.py
usage: so_script.py [-h] FILE
so_script.py: error: the following arguments are required: FILE
Note that argparse already creates the usage and help messages for you. There is no need to create them yourself. In fact, argparse will end your script before your own usage information is run.
If you do not want to have the -h switch, pass add_help=False when creating the argument parser.
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False,
)

Categories

Resources