How do I pass command line arge to runpy.
I.e. if I can do python3 -m foo --arg1=abc
how can I add --arg1=abc as sys.argv[1] when launching the process like:
import runpy
runpy.run_module('foo')
Note, a question on this subject exists here: pass command-line arguments to runpy bu the author was trying to do something else and it was never answered.
Use standard python library: argrparse
For sake of example I've static coded runpy class
from argparse import ArgumentParser as ArgParser
class runpy:
#staticmethod
def run_module(arg):
print(arg)
if __name__ == '__main__':
description = (
'runpy commandline args')
parser = ArgParser(description=description)
parser.add_argument('--arg1', default="foo", type=str,
help='help text')
options = parser.parse_args()
runpy.run_module(options.arg1)
Related
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.
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)
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>)
...
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,
)
Is there a way to use argparse with any list of strings, instead of only with sys.argv?
Here's my problem: I have a program which looks something like this:
# This file is program1.py
import argparse
def main(argv):
parser = argparse.ArgumentParser()
# Do some argument parsing
if __name__ == '__main__':
main(sys.argv)
This works fine when this program is called straight from the command line. However, I have another python script which runs batch versions of this script with different commandline arguments, which I'm using like this:
import program1
arguments = ['arg1', 'arg2', 'arg3']
program1.main(arguments)
I still want to be able to parse the arguments, but argparse automatically defaults to using sys.argv instead of the arguments that I give it. Is there a way to pass in the argument list instead of using sys.argv?
You can pass a list of strings to parse_args:
parser.parse_args(['--foo', 'FOO'])
Just change the script to default to sys.argv[1:] and parse arguments omitting the first one (which is the name of the invoked command)
import argparse,sys
def main(argv=sys.argv[1:]):
parser = argparse.ArgumentParser()
parser.add_argument("--level", type=int)
args = parser.parse_args(argv)
if __name__ == '__main__':
main()
Or, if you cannot omit the first argument:
import argparse,sys
def main(args=None):
# if None passed, uses sys.argv[1:], else use custom args
parser = argparse.ArgumentParser()
parser.add_argument("--level", type=int)
args = parser.parse_args(args)
# Do some argument parsing
if __name__ == '__main__':
main()
Last one: if you cannot change the called program, you can still do something
Let's suppose the program you cannot change is called argtest.py (I added a call to print arguments)
Then just change the local argv value of the argtest.sys module:
import argtest
argtest.sys.argv=["dummy","foo","bar"]
argtest.main()
output:
['dummy', 'foo', 'bar']
Python argparse now has a parameter nargs for add_argument (https://docs.python/3/library/argparse.html).
It allows us to have as many arguments as we want for a named parameter (here, alist)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--alist", nargs="*")
args = parser.parse_args()
print(args.alist)
All command line values that follow --alist are added to a list.
Example:
$ python3 argparse-01.py --alist fred barney pebbles "bamm bamm"
['fred', 'barney', 'pebbles', 'bamm bamm']
As you see, it is allowed to quote the arguments, but not necessary unless you need to protect a space.