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:
All arguments are mandatory. Presently if the user misses some argument then the script raises an
UnboundLocalError
exception. I then catch it and callusage()
. Is it a correct way? Should I uselen(argv)
in some ways to handle this?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".
1 Answer 1
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)