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

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

Related

How to pass an argument calling a function using ArgParse?

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.

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)

How to give an input-file as argument an read it in Python?

I am trying to give an input file as an argument in Python using argparse, but somehow, Im getting an error.
Here is my code:
from argparse import ArgumentParser
def main():
args = arg_parser.parse_args()
print('in main, args = ',args)
input_file = args.input_file
update_file_input(input_file)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description="less script")
parser.add_argument('--input_file', required=True, help="input file containing IDs and attributes to change (csv)")
args = parser.parse_args()
print('args is: ',args)
main()
I am calling the script with the following command from command line:
python updaterScript.py --input_file myCSVFile.csv
What am I doing wrong here? Why am I getting an error?
Here is the error:
Traceback (most recent call last):
File "/home/ProjectP/runtime/bin/updaterScript.py", line 11, in <module>
load_entry_point('ProjectP==1.0', 'console_scripts', 'updaterScript.py')()
File "/home/ProjectP/runtime/lib/python3.6/site-packages/ProjectP/updaterScript.py", line 10
0, in main
args = arg_parser.parse_args()
NameError: name 'arg_parser' is not defined
No need to call parse_args() in two places. Call it once, then pass the result to main() as a parameter.
def main(args):
print('in main, args = ',args)
input_file = args.input_file
update_file_input(input_file)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description="less script")
parser.add_argument('--input_file', required=True, help="input file containing IDs and attributes to change (csv)")
args = parser.parse_args()
print('args is: ',args)
main(args)
There's no reason that I can see to split the code between main() and the if block. I would just put it all in one place and move the import to the top of the file.
import argparse
def main():
parser = argparse.ArgumentParser(description="less script")
parser.add_argument('--input_file', required=True, help="input file containing IDs and attributes to change (csv)")
args = parser.parse_args()
print('args is: ',args)
input_file = args.input_file
update_file_input(input_file)
if __name__ == '__main__':
main()
Well, your args which is actually arg_parser.parse_args() is not defined. Do you import or have from somewhere the arg_parser?
Can you debug and see what arg_parser.parse_args() returns?
Well, according to your code, there's no logical reason that it would recognize arg_parse.
You are not importing it or initializing it at any point of your (seen) code.
I don't get what you're trying to do there.
Where did you come up with arg_parse?

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

Python: read command line args with argparse in addition to those coming from an external function

I have two modules in the same package, module1.py and module2.py.
In module1.py I have a function reading command-line args with argparse:
import argparse
def parse_arguments_module1():
parser = argparse.ArgumentParser()
optional = parser._action_groups.pop()
required = parser.add_argument_group('required arguments')
required.add_argument('--argA', help='argA', required=True)
required.add_argument('--argB', help='argB', required=True)
optional.add_argument('--argC', help='argC')
optional.add_argument('--argD', help='argD')
parser._action_groups.append(optional)
args = parser.parse_args()
return args
Now suppose in module2.py I import parse_arguments_module1() from module1.py and use it (this works):
from module1 import parse_arguments_module1
if __name__ == '__main__':
args = parse_arguments_module1()
print(args)
Use:
python3 module2.py --argA A --argB B --argC C
Output:
Namespace(argA='A', argB='B', argC='C', argD=None)
The question is: how to read arguments in module2.py (required and/or optional) additional to those of module1.py? (I.e. have args in main contain more arguments, just for module2.py)
You'd need to use partial parsing with parser.parse_known_args() to achieve what you want, and / or pass your arguments as a list, explicitly.
Normally, without arguments parser.parse_args() takes all values from sys.argv[1:] (so everything but the first element) as the input to parse. If there are elements in that list that can't be parsed, then an error message is printed and sys.exit(1) is called; your script would exit.
So if you want some arguments on sys.argv to go to one parser, and the remainder to another, you want to use parser.parse_known_args() and pass the remainder to the other parser.
I'd split out creating and configuring the ArgumentParser() instances from parsing; so in module1 do:
def module1_argument_parser():
parser = argparse.ArgumentParser()
optional = parser._action_groups.pop()
required = parser.add_argument_group('required arguments')
required.add_argument('--argA', help='argA', required=True)
required.add_argument('--argB', help='argB', required=True)
optional.add_argument('--argC', help='argC')
optional.add_argument('--argD', help='argD')
parser._action_groups.append(optional)
return parser
def parse_arguments_module1(args=None):
parser = module1_argument_parser()
return parser.parse_args(args)
if __name__ == '__main__':
args = parse_arguments_module1()
print(args)
and in module2, use the same structure, but re-use the parser from module1:
from module1 import module1_argument_parser
def module2_argument_parser():
parser = argparse.ArgumentParser()
# create argument switches, etc.
return parser
def parse_arguments_module2(args=None):
parser = module2_argument_parser()
return parser.parse_args(args)
if __name__ == '__main__':
module1_parser = module1_argument_parser()
module1_args, remainder = module1_parser.parse_known_args()
module2_args = parse_arguments_module2(remainder)
print(module1_args)
print(module2_args)

Categories

Resources