python argparse set behaviour when no arguments provided - python

I'm fairly new to python and I'm stuck on how to structure my simple script when using command line arguments.
The purpose of the script is to automate some daily tasks in my job relating to sorting and manipulating images.
I can specify the arguments and get them to call the relevant functions, but i also want to set a default action when no arguments are supplied.
Here's my current structure.
parser = argparse.ArgumentParser()
parser.add_argument("-l", "--list", help="Create CSV of images", action="store_true")
parser.add_argument("-d", "--dimensions", help="Copy images with incorrect dimensions to new directory", action="store_true")
parser.add_argument("-i", "--interactive", help="Run script in interactive mode", action="store_true")
args = parser.parse_args()
if args.list:
func1()
if args.interactive:
func2()
if args.dimensions:
func3()
But when I supply no arguments nothing will be called.
Namespace(dimensions=False, interactive=False, list=False)
What i want is some default behaviour if no arguements are supplied
if args.list:
func1()
if args.interactive:
func2()
if args.dimensions:
func3()
if no args supplied:
func1()
func2()
func3()
This seems like it should be fairly easy to achieve but I'm lost on the logic of how to detect all arguments are false without looping through the arguments and testing if all are false.
Update
Multiple arguments are valid together, that is why I didn't go down the elif route.
Update 2
Here is my updated code taking into account the answer from #unutbu
it doesn't seem ideal as everything is wrapped in an if statement but in the short term i couldn't find a better solution. I'm happy to accept the answer from #unutbu, any other improvements offered would be appreciated.
lists = analyseImages()
if lists:
statusTable(lists)
createCsvPartial = partial(createCsv, lists['file_list'])
controlInputParital = partial(controlInput, lists)
resizeImagePartial = partial(resizeImage, lists['resized'])
optimiseImagePartial = partial(optimiseImage, lists['size_issues'])
dimensionIssuesPartial = partial(dimensionIssues, lists['dim_issues'])
parser = argparse.ArgumentParser()
parser.add_argument(
"-l", "--list",
dest='funcs', action="append_const", const=createCsvPartial,
help="Create CSV of images",)
parser.add_argument(
"-c", "--convert",
dest='funcs', action="append_const", const=resizeImagePartial,
help="Convert images from 1500 x 2000px to 900 x 1200px ",)
parser.add_argument(
"-o", "--optimise",
dest='funcs', action="append_const", const=optimiseImagePartial,
help="Optimise filesize for 900 x 1200px images",)
parser.add_argument(
"-d", "--dimensions",
dest='funcs', action="append_const", const=dimensionIssuesPartial,
help="Copy images with incorrect dimensions to new directory",)
parser.add_argument(
"-i", "--interactive",
dest='funcs', action="append_const", const=controlInputParital,
help="Run script in interactive mode",)
args = parser.parse_args()
if not args.funcs:
args.funcs = [createCsvPartial, resizeImagePartial, optimiseImagePartial, dimensionIssuesPartial]
for func in args.funcs:
func()
else:
print 'No jpegs found'

You could append_const the funcs to an attribute, args.funcs, and then use one if-statement to supply the default behavior if no options are set:
if not args.funcs:
args.funcs = [func1, func2, func3]
import argparse
def func1(): pass
def func2(): pass
def func3(): pass
parser = argparse.ArgumentParser()
parser.add_argument(
"-l", "--list",
dest='funcs', action="append_const", const=func1,
help="Create CSV of images", )
parser.add_argument(
"-i", "--interactive",
dest='funcs', action="append_const", const=func2,
help="Run script in interactive mode",)
parser.add_argument(
"-d", "--dimensions",
dest='funcs', action='append_const', const=func3,
help="Copy images with incorrect dimensions to new directory")
args = parser.parse_args()
if not args.funcs:
args.funcs = [func1, func2, func3]
for func in args.funcs:
print(func.func_name)
func()
% test.py
func1
func2
func3
% test.py -d
func3
% test.py -d -i
func3
func2
Note that, unlike your original code, this allows the user to control the order the functions are called:
% test.py -i -d
func2
func3
That may or may not be desireable.
In response to Update 2:
Your code will work just fine. However, here is another way you could organize it:
Instead of nesting the main program inside an if clause, you could
use
if not lists:
sys.exit('No jpegs found')
# put main program here, unnested
sys.exit will print No jpegs found to stderr and terminate with exit code 1.
Although I originally suggested using functools.partial, another -- perhaps simpler -- way now comes to mind: Instead of
for func in args.funcs:
func()
we could say
for func, args in args.funcs:
func(args)
All we need to do is store a tuple (func, args) in args.func
instead of the function alone.
For example:
import argparse
import sys
def parse_args(lists):
funcs = {
'createCsv': (createCsv, lists['file_list']),
'resizeImage': (resizeImage, lists['resized']),
'optimiseImage': (optimiseImage, lists['size_issues']),
'dimensionIssues': (dimensionIssues, lists['dim_issues']),
'controlInput': (controlInput, lists)
}
parser = argparse.ArgumentParser()
parser.add_argument(
"-l", "--list",
dest='funcs', action="append_const", const=funcs['createCsv'],
help="Create CSV of images",)
parser.add_argument(
"-c", "--convert",
dest='funcs', action="append_const", const=funcs['resizeImage'],
help="Convert images from 1500 x 2000px to 900 x 1200px ",)
parser.add_argument(
"-o", "--optimise",
dest='funcs', action="append_const", const=funcs['optimiseImage'],
help="Optimise filesize for 900 x 1200px images",)
parser.add_argument(
"-d", "--dimensions",
dest='funcs', action="append_const", const=funcs['dimensionIssues'],
help="Copy images with incorrect dimensions to new directory",)
parser.add_argument(
"-i", "--interactive",
dest='funcs', action="append_const", const=funcs['controlInput'],
help="Run script in interactive mode",)
args = parser.parse_args()
if not args.funcs:
args.funcs = [funcs[task] for task in
('createCsv', 'resizeImage', 'optimiseImage', 'dimensionIssues')]
return args
if __name__ == '__main__':
lists = analyseImages()
if not lists:
sys.exit('No jpegs found')
args = parse_args(lists)
statusTable(lists)
for func, args in args.funcs:
func(args)

You can handle this by checking if the number of args equals 1. meaning only your python command was passed.
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument("-l", "--list", help="Create CSV of images", action="store_true")
parser.add_argument("-d", "--dimensions", help="Copy images with incorrect dimensions to new directory", action="store_true")
parser.add_argument("-i", "--interactive", help="Run script in interactive mode", action="store_true")
args = parser.parse_args()
if len(sys.argv)==1:
# display help message when no args are passed.
parser.print_help()
sys.exit(1)

Is that what you want?
if args.list:
func1()
if args.interactive:
func2()
if args.dimensions:
func3()
if not any(vars(args).values()):
func1()
func2()
func3()
(Thanks #J.F.Sebastian for the any version)

Related

passing a dict with arguments to argparse

I am using a codebase that expects a large set of argument via command line using argparse library and I neet to call that code inside a loop and inject the arguments via dictionary and not via command line without changing that codebase, I call the code as follow:
parser = argparse.ArgumentParser('Training', parents=[get_args_parser()])
args = parser.parse_args()
main(args)
Where get_args_parser() is a large list of arguments and defaults such as :
def get_args_parser():
parser = argparse.ArgumentParser('Set transformer detector', add_help=False)
parser.add_argument('--lr', default=1e-4, type=float)
parser.add_argument('--lr_backbone', default=1e-5, type=float)
parser.add_argument('--batch_size', default=2, type=int)
parser.add_argument('--weight_decay', default=1e-4, type=float)
parser.add_argument('--epochs', default=300, type=int)
parser.add_argument('--lr_drop', default=200, type=int)
...
If i need to pass a dictionary , as arguments , like:
argdict = {'lr_drop':20,'batch_size':5}
How can I do it?
you should use like this:
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dict", required=True, help="Your dict as string", default="{}")
args = vars(ap.parse_args())
argdict = eval(args["dict"])
print(argdict)
# or you cam print some dict specific var
print(argdict["name"]) #Jasar
the you can call your file like it:
python3 file.py -d '{"name":"Jasar"}'
using some clues by #Jaser and #chepner , what i did is as follow:
args_to_argdict = {'a':1 , 'b':49 ,'c': 'text' }
parser = argparse.ArgumentParser(parents=[get_args_parser()])
args = parser.parse_args()
arg_dict = vars(args)
for key,value in args_to_argdict.items():
arg_dict[key]= value
so that the args value change , then i run the main :
main(args)
with the modified args .

How can i receive 1 argument with the python library parser and use just the argument?

I'm doing a command line application to verify if a website is active, and i want to receive one argument, but the parser needs receive an argument and a valor for this argument, So my question is: How can i use just 1 argument ?
This is my code:
if __name__=="__main__":
import requests
import datetime
from time import sleep
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('u', help="Unique verification")
parser.add_argument('c', help="Continuos verification")
parser.add_argument('s', help="Check and save to file")
parser.add_argument('d', help="Documentation")
args = parser.parse_args()
main(parser.parse_args)
My function main receives a char, how can i use a char via command line?
You are currently creating mandatory positional arguments, so that a call like
python3 verify.py foo bar baz bye
would result in
args.u == 'foo'
args.c == 'bar'
args.s == 'baz'
args.d == 'bye'
You want to define four options, using the store_true action so that providing the option will set a flag from its default False value to True.
parser = argparse.ArgumentParser()
parser.add_argument('-u', action='store_true', help="Unique verification")
parser.add_argument('-c', action='store_true', help="Continuos verification")
parser.add_argument('-s', action='store_true', help="Check and save to file")
parser.add_argument('-d', action='store_true', help="Documentation")
args = parser.parse_args()
Now a call like
python3 verify.py -u -s
would result in
args.u == True
args.c == False
args.s == True
args.d == False
If you want to further restrict the user to exactly one of the four options, use a mutual-exclusion group.
# Same as above, calling group.add_argument, not parser.add_argument
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-u', action='store_true', help="Unique verification")
group.add_argument('-c', action='store_true', help="Continuos verification")
group.add_argument('-s', action='store_true', help="Check and save to file")
group.add_argument('-d', action='store_true', help="Documentation")
args = parser.parse_args()

Python argparse - Mutually exclusive group with default if no argument is given

I'm writing a Python script to process a machine-readable file and output a human-readable report on the data contained within.
I would like to give the option of outputting the data to stdout (-s) (by default) or to a txt (-t) or csv (-c) file. I would like to have a switch for the default behaviour, as many commands do.
In terms of Usage:, I'd like to see something like script [-s | -c | -t] input file, and have -s be the default if no arguments are passed.
I currently have (for the relevant args, in brief):
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_true')
group.add_argument('-c', '--csv', action='store_true')
group.add_argument('-t', '--txt', action='store_true')
args = parser.parse_args()
if not any((args.stdout, args.csv, args.txt)):
args.stdout = True
So if none of -s, -t, or -c are set, stdout (-s) is forced to True, exactly as if -s had been passed.
Is there a better way to achieve this? Or would another approach entirely be generally considered 'better' for some reason?
Note: I'm using Python 3.5.1/2 and I'm not worried about compatibility with other versions, as there is no plan to share this script with others at this point. It's simply to make my life easier.
You could have each of your actions update the same variable, supplying stdout as the default value for that variable.
Consider this program:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument(
'-s', '--stdout', action='store_const', dest='type', const='s', default='s')
group.add_argument(
'-c', '--csv', action='store_const', dest='type', const='c')
group.add_argument(
'-t', '--txt', action='store_const', dest='type', const='t')
args = parser.parse_args()
print args
Your code could look like:
if args.type == 's':
ofile = sys.stdout
elif args.type == 'c':
ofile = ...
...
First alternative:
Rather than arbitrarily choose one of the .add_argument()s to specify the default type, you can use parser.set_defaults() to specify the default type.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_const', dest='type', const='s')
group.add_argument('-c', '--csv', action='store_const', dest='type', const='c')
group.add_argument('-t', '--txt', action='store_const', dest='type', const='t')
parser.set_defaults(type='s')
args = parser.parse_args()
print args
Second alternative:
Rather than specify the type as an enumerated value, you could store a callable into the type, and then invoke the callable:
import argparse
def do_stdout():
# do everything that is required to support stdout
print("stdout!")
return
def do_csv():
# do everything that is required to support CSV file
print("csv!")
return
def do_text():
# do everything that is required to support TXT file
print("text!")
return
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_const', dest='type', const=do_stdout)
group.add_argument('-c', '--csv', action='store_const', dest='type', const=do_csv)
group.add_argument('-t', '--txt', action='store_const', dest='type', const=do_text)
parser.set_defaults(type=do_stdout)
args = parser.parse_args()
print args
args.type()
You can "cheat" with sys.argv :
import sys
def main():
if len(sys.argv) == 2 and sys.argv[1] not in ['-s', '-c', '-t', '-h']:
filename = sys.argv[1]
print "mode : stdout", filename
else:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout')
group.add_argument('-c', '--csv')
group.add_argument('-t', '--txt')
args = parser.parse_args()
if args.stdout:
print "mode stdout :", args.stdout
if args.csv:
print "mode csv :", args.csv
if args.txt:
print "mode txt :", args.txt
if __name__ == "__main__":
main()

Python Argparse add_mutually_exclusive_group()- Need ether 2 args or just 1 args

I am using Python's 2.7 argparse. I need it to where the user can enter arguements (-a and -b) OR (-c). But but not (-a and -b) and (-c) together. If (-a and -b) are chosen by the user instead of -c, both of them are required. How could I do this?
group_key = member_add.add_mutually_exclusive_group(required=True)
group_key.add_argument('-a',
required=True)
group_key.add_argument('-b',
required=True)
group_key.add_argument('-c',
required=True)
The current implementation of add_mutually_exclusive_group() doesn't actually
create mutually exclusive groups. There is a open bug to address this behavior.
Having said that, you could achieve this using:
(a) subcommands
Example code :
# create the top-level parser
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='help for subcommand')
# create the parser for the "cmd_1" command
parser_a = subparsers.add_parser('cmd_1', help='help for cmd_1')
parser_a.add_argument('-a', type=str, help='help for a')
parser_a.add_argument('-b', type=str, help='help for b')
# create the parser for the "cmd_2" command
parser_b = subparsers.add_parser('cmd_2', help='help for cmd_2')
parser_b.add_argument('-c', type=str, help='help for c')
parser.parse_args()
(b) Small hack for the simple case like yours :
ap=argparse.ArgumentParser()
# 1st group
ap.add_argument("-a", dest="value_a", help="help for a", required=False)
ap.add_argument("-b", dest="value_b", help="help for b", required=False)
# 2nd group
ap.add_argument("-c", dest="value_c", help="help for b", required=False)
args = ap.parse_args()
if (args.value_a or args.value_b):
if (args.value_a or args.value_b) and args.value_c:
print "-a and -b|-c are mutually exclusive ..."
elif not (args.value_a and args.value_b):
print "both -a and -b are required."

Python optparse make_option() equivalent in argparse

From the OPTPARSE library reference:
option_list = [
make_option("-f", "--filename",
action="store", type="string", dest="filename"),
make_option("-q", "--quiet",
action="store_false", dest="verbose"),
]
parser = OptionParser(option_list=option_list)
Like the above example, I want to make a option list using make_option and pass it to a decorator which instantiates the parser and adds the arguments.
How can this be achieved in argparse? Is there a way to populate the parser other than parse_args()?
You can give argparse.ArgumentParser a list of parent parsers:
parent = argparse.ArgumentParser(add_help=False)
parent.add_argument('-f','--filename')
parent.add_argument('-q','--quiet',action='store_false',dest='verbose')
parser = argparse.ArgumentParser(parents=[parent])
...
namespace = parser.parse_args()
def process_args():
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--filename', dest='filename', type=string, action='store')
args = parser.parse_args()
return args
if __name__ == '__main__':
args = process_args()

Categories

Resources