Python Command Line Arguments Syntax Error - python

I am trying to print out the command line arguments, however when I try to have two print statements, I get a syntax error. Is there a reason for that?
This is my terminal input: python3 test.py example_Resume.pdf softwareengineering
Also, how would I deal with a command-line argument that has a space in between?
import sys
def main(argv):
{
print(f"Name of the script : {sys.argv[0]=}")
print(f"Arguments of the script : {sys.argv[1:]=}") // This line errors
}
if __name__ == "__main__":
main(sys.argv[1:])

The best way to deal with command-line arguments is to use argparse package which takes care of shell injection, optional, default arguments.
Documentation: Argpase
Here is a sample I use for projects.
import argparse
# >>>>>>>>>>>>>>>>>>>>>>> ARGUMENT PARSING <<<<<<<<<<<<<<<<<<<<<<<<<<<<
def args():
"""
Used to get the list of arguments paseed to the class
"""
parser = argparse.ArgumentParser()
parser.add_argument('-r','--raw_file',type=str,
help='It is used to get the raw input file for parsing',
required=True)
args = parser.parse_args()
return args.raw_file
def main():
# PARSED RAW FILE
raw_file=self.args()
if __name__ == '__main__':
main()

In addition to what the other commenters pointed out, your issue is with the curly braces.
I tried your code without them:
import sys
def main(argv):
print(f"Name of the script : {sys.argv[0]=}")
print(f"Arguments of the script : {sys.argv[1:]=}")
if __name__ == "__main__":
main(sys.argv[1:])
Curly braces mean something different in Python.
The question about passing the args when you don't use them in main is valid.

Related

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)

Using the '--help' command in argparse

I am using the argparse library but for whatever reason I'm having difficult printing the -h argument. Here is the entire source I have:
# df.py
import argparse
parser = argparse.ArgumentParser(description='Dedupe assets in our library.')
parser.add_argument('--masters', nargs='?', default=None, type=int, help='Enter one or more ids.')
if __name__ == '__main__':
print ('hi')
I was under the impression that entering in the --h flag via:
$ python df.py --help
Would automatically print the help stuff for the file using argparse but I seem to be making false assumptions. It seems like I also have to add in something like this into my code?
if '--help' in sys.argv: print (parser.parse_args(['-h']))
What is the 'proper' way to print out the help args when using the argparse library?
You forgot to actually parse the arguments; if you put parser.parse_args() in after defining the parser, it would respond to -h/--help. Typically, you'd do something like:
args = parser.parse_args()
so that the args object can be used to access the parsed argument data.
I'll also note that the argument parsing should almost certainly be controlled by the if __name__ == '__main__': guard; if you're not being invoked as the main script, parsing the command line is unusual, to say the least. Idiomatic code would look something like:
# df.py
def main():
import argparse # Could be moved to top level, but given it's only used
# in main, it's not a terrible idea to import in main
parser = argparse.ArgumentParser(description='Dedupe assets in our library.')
parser.add_argument('--masters', nargs='?', type=int, help='Enter one or more ids.')
args = parser.parse_args()
print ('hi')
# Do something with args.masters or whatever
if __name__ == '__main__':
main()

How to obtain a python command line argument if only it's a string

I'm making my own python CLI and i want to pass only String arguments
import sys
import urllib, json
# from .classmodule import MyClass
# from .funcmodule import my_function
def main():
args = sys.argv[1:]
#print('count of args :: {}'.format(len(args)))
#for arg in args:
# print('passed argument :: {}'.format(arg))
#always returns true even if i don't pass the argument as a "String"
if(isinstance(args[0], str)):
print('JSON Body:')
url = args[0]
response = urllib.urlopen(url)
data = json.loads(response.read())
print(data)
# my_function('hello world')
# my_object = MyClass('Thomas')
# my_object.say_name()
if __name__ == '__main__':
main()
I execute it by api "url" and this is the correct output:
Although when i'm trying to execute api url without passing it as a String my output is a little odd:
How can i accept only String arguments?
What I've tried so far:
Found this solution here but it didn't work for me (couldn't recognize the join() function)
problem isn't a python issue. It's just that your URL contains a &, and on a linux/unix shell, this asks to run your command in the background (and the data after & is dropped). That explains the [1]+ done output, with your truncated command line.
So you have to quote your argument to avoid it to be interpreted (or use \&). There's no way around this from a Un*x shell (that would work unquoted from a Windows shell for instance)

How to use python argparse with args other than sys.argv?

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.

how to pass arguments to a module in python 2.x interactive mode

I'm using Python 2.7, and I have the following simple script, which expects one command line argument:
#!/usr/bin/env python
import sys
if (len(sys.argv) == 2):
print "Thanks for passing ", sys.argv[1]
else:
print "Oops."
I can do something like this from the command line:
My-Box:~/$ ./useArg.py asdfkjlasdjfdsa
Thanks for passing asdfkjlasdjfdsa
or this:
My-Box:~/$ ./useArg.py
Oops.
I would like to do something similar from the interactive editor:
>>> import useArg asdfasdf
File "<stdin>", line 1
import useArg asdfasdf
^
SyntaxError: invalid syntax
but I don't know how. How can I pass a parameters to import/reload in the interactive editor ?
You can't. Wrap your code inside the function
#!/usr/bin/env python
import sys
def main(args):
if (len(args) == 2):
print "Thanks for passing ", args[1]
else:
print "Oops."
if __name__ == '__main__':
main(sys.argv)
If you execute your script from command line you can do it like before, if you want to use it from interpreter:
import useArg
useArg.main(['foo', 'bar'])
In this case you have to use some dummy value at the first position of the list, so most of the time much better solution is to use argparse library. You can also check the number of command line arguments before calling the main function:
import sys
def main(arg):
print(arg)
if __name__ == '__main__':
if len(sys.argv) == 2:
main(sys.argv[1])
else:
main('Oops')
You can find good explanation what is going on when you execute if __name__ == '__main__': here: What does if __name__ == "__main__": do?

Categories

Resources