Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python argparse set behaviour when no arguments provided

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'
like image 603
d.learious Avatar asked Jan 16 '13 12:01

d.learious


2 Answers

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)
like image 56
unutbu Avatar answered Nov 15 '22 17:11

unutbu


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)
like image 35
aidanmelen Avatar answered Nov 15 '22 17:11

aidanmelen