6
\$\begingroup\$

I am learning getopt in Python to parse and validate command line inputs:

#!/usr/bin/python
'''\nMailing Script
Usage: python script.py -y <year> -c <type_user>"
Arguments:
 -y, --year       any value from 2008 to 2013
 -c, --campaign   any value in:
                    '80c', '80d', 'allowances', 'late_filing', 
                    'exempt_income', 'other_sources'    
Flags:
 -h      help
'''     
import sys, getopt
first_yr, last_yr = (2008, 2013)
campaign_type = (
    '80c', '80d', 'allowances', 'late_filing', 
    'exempt_income', 'other_sources',
)

def usage():
    print sys.exit(__doc__)

def parse_arguments(argv):
    try:
        opts, args = getopt.getopt(argv[1:], "y:c:", ["year=", "campaign="])
    except getopt.GetoptError:
        usage() 
    for opt, arg in opts:
        if opt == '-h':
            usage()
        elif opt in ("-y", "--year"):
            target_year = arg
            # customized exception
            try:
                if not target_year in map(str, range(first_yr, last_yr+1)):
                    raise Exception()
            except:
                sys.exit("Argument Error: Invalid year passed. \n" 
                         "One valid year is within (%s, %s)" % (first_yr, last_yr)
                )                               
        elif opt in ("-c", "--campaign"):
            campaign = arg.lower()
            try: 
                if not campaign in campaign_type:
                    raise Exception()
            except Exception, e:
                sys.exit("Argument Error: Invalid 'Campaign type' passed. \n" 
                         "A valid value can be any of %s" % str(campaign_type)
                )

    # to avoid 'UnboundLocalError' Exception when one argument not passed
    try:
        target_year, campaign
    except UnboundLocalError, u: 
        usage() 

    print 'Year : ', target_year 
    print 'campaign: ', campaign 

    # return argument values
    return (target_year, campaign)

if __name__ == "__main__":
    target_year, campaign = parse_arguments(sys.argv)

Presently I didn't learn 16.4. argparse. I will learn it in the next step.

I want to improve my script as follows:

  1. All arguments are mandatory. Presently if the user misses some argument then the script raises an UnboundLocalError exception. I then catch it and call usage(). Is it a correct way? Should I use len(argv) in some ways to handle this?

  2. I check for correct values of arguments. If any argument value is wrong then I explicitly raise an exception and print an error message in the except clause. I am again not sure whether it's a preferable way to write this kind of code. I feel it adds one more level of nested blocks.

Some runs of this script:

$ python opt.py -y 2012 -c 80c
Year :  2012
campaign:  80c

Correct as I want:

$ python opt.py -year=2012 
Argument Error: Invalid year passed. 
One valid year is within (2008, 2013)

I think I should print message something like "insufficient arguments passed" or "use --year=2012 or -y 2012".

\$\endgroup\$
6
\$\begingroup\$

I think you have too many try/excepts. More than one of them raise and catch their own exceptions. I've made some changes that improves readability.

For clarity the new code created a new exception class and raises it when appropriate.

Also using getopt's own handler for bogus input. The else in the main loop is redundant but it can also serve as a fallback in case you make a mistake by allowing an arg but forget to process it.

Pay attention to the wording: if target_year not in map( ...

reads better than: if not target_year in map( ...

#!/usr/bin/python
'''\nMailing Script
Usage: python script.py -y <year> -c <type_user>"
Arguments:
 -y, --year       any value from 2008 to 2013
 -c, --campaign   any value in:
                    '80c', '80d', 'allowances', 'late_filing', 
                    'exempt_income', 'other_sources'    
Flags:
 -h      help
'''     
import sys
import getopt
first_yr, last_yr = (2008, 2013)
campaign_type = (
    '80c', '80d', 'allowances', 'late_filing',
    'exempt_income', 'other_sources',
)


class ArgumentError(Exception):
    pass


def usage():
    print sys.exit(__doc__)


def parse_arguments(argv):
    target_year = None
    campaign = None

    try:
        opts, args = getopt.getopt(argv[1:], "y:c:", ["year=", "campaign="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print str(err)  # will print something like "option -a not recognized"
        usage()

    for opt, arg in opts:
        if opt == '-h':
            usage()

        elif opt in ("-y", "--year"):
            target_year = arg
            if target_year not in map(str, range(first_yr, last_yr + 1)):
                raise ArgumentError("Argument Error: Invalid year passed. \n "
                                    "One valid year is within (%s, %s)" % (first_yr, last_yr))

        elif opt in ("-c", "--campaign"):
            campaign = arg.lower()
            if campaign not in campaign_type:
                raise ArgumentError("Argument Error: Invalid 'Campaign type' passed. "
                                    "A valid value can be any of %s" % str(campaign_type))
        else:
            raise ArgumentError("Bad argument: I don't know what %s is" % arg)

    if target_year is None or campaign is None:
        raise ArgumentError("You need to supply both -y and -c")

    print 'Year : ', target_year 
    print 'campaign: ', campaign 

    # return argument values
    return target_year, campaign



if __name__ == "__main__":
    target_year, campaign = parse_arguments(sys.argv)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.