Call function not all functions using argparse shortcuts - python

All I want is this.
a = first()
print("aaa updated")
b = second()
print("apt updated")
c = third()
print("fax updated")
import argparse parser = argparse.ArgumentParser()
parser.add_argument("a", help="<description>", action="store_true")
parser.add_argument("b", help="<description>", action="store_true")
parser.add_argument("c", help="<description>", action="store_true")
args = parser.parse_args()
To call either a or b or c using argparse. I don't want all of them called. I want to be able to run on CMD like myscript.py -a and that will call a.

sample script:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('--cmd', choices=['one','two','three'])
parser.add_argument('-a', action='store_true')
args = parser.parse_args()
print(args)
if args.a: # is True
print('A is True')
if args.cmd == 'one':
print('do action one')
elif args.cmd == 'two':
print('doing action two')
else:
print('doing three')
sample runs:
1651:~/mypy$ python3 stack60822121.py -h
usage: stack60822121.py [-h] [--cmd {one,two,three}] [-a]
optional arguments:
-h, --help show this help message and exit
--cmd {one,two,three}
-a
1651:~/mypy$ python3 stack60822121.py -a
Namespace(a=True, cmd=None)
A is True
doing three
1651:~/mypy$ python3 stack60822121.py --cmd one
Namespace(a=False, cmd='one')
do action one
argparse is just a way of parsing/decoding the user input. It does not perform actions. For that use ordinary Python logic. That's your code. At its most basic argparse set values to strings, boolean values, and numbers. Use those.

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

How to use `argparse` to pass arguments both from command line and from code?

I have a python program that takes in many arguments and run. With argparse, I can define the arguments, their default values, their explanations, and it can be used as a convenient container. So it's all good for passing arguments from command line.
But can I also use it to pass the arguments from code, for API call?
Yes, certainly argparse can be used to pass arguments both from command line and from code.
For example:
import argparse
# Define default values
parser = argparse.ArgumentParser()
parser.add_argument('--foo', default=1, type=float, help='foo')
# Get the args container with default values
if __name__ == '__main__':
args = parser.parse_args() # get arguments from command line
else:
args = parser.parse_args('') # get default arguments
# Modify the container, add arguments, change values, etc.
args.foo = 2
args.bar = 3
# Call the program with passed arguments
program(args)
With vars() you can use your args like dictionaries.
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-f", "--foo")
parser.add_argument("-b", "--bar")
args = vars(parser.parse_args())
print(f"args foo is {args['foo']}")
print(f"args bar is {args['bar']}")
Result when you execute and parse some arguments look like.
python3 test.py --foo cat --bar dog
args foo is cat
args bar is dog
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-f", "--file", dest="filename",
help="write report to FILE", metavar="FILE")
parser.add_argument("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
args = parser.parse_args()
When running this file from terminal, you can pass the arguments in the following manner:
python36 commandline_input.py 10
python36 commandline_input.py 10 --foo 12
The first positional argument is mandatory, the second is optional therefore you need a flag (--foo).
commandline_input.py:
import argparse
def main(mandatory_arg, optional_arg):
# your program
pass
if __name__ == '__main__':
parser = argparse.ArgumentParser()
# mandatory
parser.add_argument('bar', help='Some Bar help')
# optional
parser.add_argument('-f', '--foo', default='default foo value',
help='Some foo help')
args = parser.parse_args()
# mandatory args
print(args.bar, '(man)')
# optional args
if args.foo:
print(args.foo, '(opt)')
# your API call
main(args.bar, [args.foo])

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 - unittest trying to call imported custom argparser

I have a unittest that wants to call an imported module to do both parse_os based on the unittest's command-line option but it seems unittest does not recognize the option, any ideas:
./python testParser.py --mac
option --mac not recognized
Usage: testParser.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
testParser.py - run default set of tests
testParser.py MyTestSuite - run suite 'MyTestSuite'
testParser.py MyTestCase.testSomething - run MyTestCase.testSomething
testParser.py MyTestCase - run all 'test*' test methods
in MyTestCase
I want to run my unittest program like this: python testParser.py --mac
EDITTED: Works now by changing 'unittest.main()' to:
runner = unittest.TextTestRunner(stream=stderr_file)
itersuite = unittest.TestLoader().loadTestsFromTestCase(TT28046_ForensicSearchSmokeTest)
runner.run(itersuite)
Unittest program:
import logging
import unittest
from myargparse import *
class MyTest(unittest.TestCase):
def test_parse_os(self):
## Parse the args:
self.install = install_sw(parse_os(arg=""))
print 'Which os? %s' % self.install
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
# get the default logger
logger = logging.getLogger()
# add a file handler
logger.addHandler(logging.FileHandler('stdout.txt', mode='w'))
# set up a stream for all stderr output
stderr_file = open('stderr.txt', 'w')
# attach that stream to the testRunner
unittest.main(testRunner=unittest.TextTestRunner(stream=stderr_file))
My imported module:
import argparse
import os
import sys
def parse_os(arg):
my_os = ''
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mac",
action="store_true")
parser.add_argument("-w", "--win",
action="store_true")
args = parser.parse_args()
if args.mac:
print 'Mac'
my_os = "Mac"
if args.win:
print 'Windows'
my_os = "Windows"
return my_os
def install_sw(my_os):
installed_os = None
if my_os == 'Mac':
print 'Installing Mac...'
installed_os = 'Mac'
if my_os == 'Windows':
print 'Installing Windows...'
installed_os = 'Windows'
return installed_os
The sys.argv variable is a simple list so you can modify/replace it at your wish.
I'd consider using a context manager in this case, on the lines of:
class SysArgv(object):
def __init__(self, argv):
self._old_argv = sys.argv
sys.argv = argv
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
sys.argv = self._old_argv
return False
And used as:
In [4]: with SysArgv(['a', 'b', 'c']):
...: print(sys.argv)
...:
['a', 'b', 'c']
In your case simple wrap the test code like:
with SysArgv(['the_module_name.py', '--mac']):
# test code goes here
and the argparse module will see the arguments you wants.
As for passing the arguments to the unittest module when running the tests, it's possible passing the argv argument to unittest.main. From the documentation:
The `argv` argument can be a list of options passed to the program, with the first element being the program name. If not specified or `None`, the values of `sys.argv` are used.
However in this case you should modify the sys.argv variable before calling unittest.main:
if __name__ == '__main__':
options = ['name_of_module.py'] + sys.argv[-1:] # last argument as option for the test
with SysArgv(sys.argv[:-1]): # or modify how you want
unittest.main(argv=options)
Have you tried using just '-m' instead of '--mac'?
You may also try:
import optparse
parser = optparse.OptionParser()
parser.add_option("-m", "--mac",
dest="mac",
action="store_true",
help="Run as Mac")
parser.add_option("-w", "--win",
dest="win",
action="store_true",
help="Run as Win")
(options, args) = parser.parse_args()
Thank you all for your suggestions but I decided to go with this to limit the changes to my program.
Instead of calling 'unittest.main()', I just changed to call the following:
runner = unittest.TextTestRunner(stream=stderr_file)
itersuite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
runner.run(itersuite)
Based on all the answers here, I originally did this simple hack, and it worked:
# Change sys.argv before unittest tries to parse our args
sys.argv = [sys.argv[0]] # Replace with only the first arg
unittest.main()
The I realized I could still use all the unitest command line args, and in my case I was just passing a bunch of paths, so anything starting with a "-" could just be passed on and there is no need to hack anything since unittest.main() has an argv argument
# Pass on options, and more importantly, don't pass on ALL args
options = [sys.argv[0]] + [a for a in sys.argv if a.startswith("-")]
unittest.main(argv=options)
Just filter out all argparse and not arparse parameters and send then ones not being argparse arguments to sys.argv which is the one unittest uses:
args, notknownargs = parser.parse_known_args()
sys.argv[1:] = notknownargs

Categories