I've google'd quite a bit, and read the argparse documentation that I think suggests using something with vars(). I get the Namespace violation as expected, I just cant figure out the path around this issue.
Essentially, I would like to take an argparse multi-value argument and create a list from those values so I can run a for-loop through them. This is to interface with our VNX array to reset the data snapshot on all the Developer environments.
When I run the command I can see the argparse is getting the values correctly, but its throwing the Namespace exception and not actually using the values for the argument.
Much appreciation for any guidance, even a link to some better docs that will explain my problem better. I know the issue, and how I want to fix it, I'm just not sure what to even read(or google) to get around this syntax-wise?
This is what I get when i run the code:
[root#robot.lipsum.com tmp]# ./envrestore.py -e dev1 dev2 dev3
Namespace(myenv=['dev1', 'dev2', 'dev3'])
Traceback (most recent call last): File "./envrestore.py", line 43, in
run_create_snap() File "./envrestore.py", line 36, in run_create_snap
for e in myenv: TypeError: 'Namespace' object is not iterable
[root#robot.lipsum.com tmp]#
#!/usr/bin/env python
import pexpect, sys, datetime, argparse, time
from fabric.api import *
parser = argparse.ArgumentParser()
parser.add_argument('-e', '--myenv', nargs='*', type=str)
print parser.parse_args()
array = "vnx.lipsum.com"
seckey = "/opt/Navisphere/blah"
myenv = parser.parse_args()
dbhosts = ['mongo01', 'mysql01']
# !! DO NOT CHANGE IDs !!
lunpnum = "0000000"
mongo_plunid = "3"
mysql_plunid = "4"
def delete_snap(env=myenv, host=dbhosts):
child = pexpect.spawn('naviseccli -secfilepath %s -h %s snap -destroy -id %s-%s-snap' % (seckey, array, host, env))
print child
child.logfile = sys.stdout
child.expect('Are you sure you want to perform this operation\?\(y\/n\):')
child.sendline('n')
def create_snap(env=myenv, host=dbhosts, lunid=''):
print "naviseccli -secfilepath %s -h %s snap -create -res %s -name %s-%s-snap -allowReadWrite yes" % (seckey, array, lunid, host, env)
def run_delete_snap():
for e in myenv:
for h in dbhosts:
delete_snap(env=e, host=h)
def run_create_snap():
for e in myenv:
for h in dbhosts:
if "mysql" in h:
create_snap(env=e, host=h, lunid=mysql_plunid)
elif "mongo" in h:
create_snap(env=e, host=h, lunid=mongo_plunid)
run_create_snap()
I believe the problem is in what you are passing as myenv:
myenv = parser.parse_args()
I think you mean
myenv = parser.parse_args().myenv
Cheers!
myenv is the argparse.Namespace instance itself. To get the values in the option named myenv, use myenv.myenv.
for e in myenv.myenv:
print(e)
Or, to make the code clearer, name the Namespace something else:
args = parser.parse_args()
for e in args.myenv:
...
Related
So I am trying to implement basic try and except logic in my code to read and hand exceptions while reading the command line args:
I have been able to accomplish this. but I want to explicitly say while throwing an error which argument is missing like 1st or 2nd.
Here is my code.
import sys
import logging
try:
job_name = sys.argv[1]
odate = sys.argv[2]
except IndexError as err:
print('OS error: {0}'.format(err))
else:
print('Correct arguments were provided')
how could I produce an error to let the user know which arg is missing?
The exception IndexError doesn't include the accessed index that resulted to error, you can examine the stack traceback via traceback.format_exc() or sys.exc_info() or something else just to capture the accessed index but that would be too complicated.
Alternative Solution 1
Check first if the length of the arguments is correct and act accordingly:
ARG_COUNT = 2
assert len(sys.argv) == ARG_COUNT + 1
# No more need of try-except block
job_name = sys.argv[1]
odate = sys.argv[2]
Alternative Solution 2
This is the preferable solution. Use argparse instead. It handles the behavior that you need. You can also customize the settings for each argument further e.g. specifying the expected data type of an argument.
import argparse
parser = argparse.ArgumentParser(description='Process some inputs.')
parser.add_argument('job_name')
parser.add_argument('odate')
args = parser.parse_args()
print(args, args.job_name, args.odate)
Output
$ python script.py
usage: script.py [-h] job_name odate
script.py: error: the following arguments are required: job_name, odate
$ python script.py some
usage: script.py [-h] job_name odate
script.py: error: the following arguments are required: odate
$ python script.py some thing
Namespace(job_name='some', odate='thing') some thing
After importing argparse (among others that I needed) I wrote the following piece of code:
parser = argparse.ArgumentParser()
(..some code with optional arguments...)
requiredArguments = parser.add_argument_group("Required arguments")
(..some code with required arguments...)
requiredArguments.add_argument("--extension", "-ext", help="set the extension", type=str)
args = parser.parse_args()
if args.version:
print("blahblahblah")
elif args.example:
print("blahblahblah")
elif args.width:
screen_width = "%i" % args.width
elif args.height:
screen_height = "%i" % args.height
elif args.sequence:
sequence = "%i" % args.sequence
elif args.url:
url = "%s" % args.url
elif args.ext:
ext = "%s" % args.ext
else:
print("blahblahblah")
[Some additional code irrelevant to the case as it is related to screenshots]
And in the following piece of code is where the problem arises:
number = 0
while number < args.sequence:
url = args.url+str(number)+args.ext
On my terminal I have specified all the necessary arguments (or at least I believe I have):
python script.py -k [keyword] -w [screen_width] -hg [screen_height] -s 10 -u "url" -ext "extension_to_the_url"
Regardless of the positioning of the arguments, of splitting "url = args.url+str(number)+args.ext" in several parts, using and not using brackets, and a long etcetera (as well as also trying "nargs"), the error is the same:
AttributeError: 'Namespace' object has no attribute 'ext'
There must be something wrong with how I'm using the 'ext' argument, because in a previous version of the script where 'ext' was not provided, Python didn't complain at all.
I'm new to Python and I'm running out of ideas. I read there's a bug that shows this error and that basically it means too few arguments were provided? But that would make no sense since all the arguments were provided via command line...
Any ideas?
Thanks in advance!
add dest='ext', like that:
requiredArguments.add_argument("--extension", "-ext", action='store', dest='ext', help="set the extension", type=str)
and use like:
python stack_args_exp.py -ext 23
I am trying to add command line options to my script, using the following code:
import argparse
parser = argparse.ArgumentParser('My program')
parser.add_argument('-x', '--one')
parser.add_argument('-y', '--two')
parser.add_argument('-z', '--three')
args = vars(parser.parse_args())
foo = args['one']
bar = args['two']
cheese = args['three']
Is this the correct way to do this?
Also, how do I run it from the IDLE shell? I use the command
'python myprogram.py -x foo -y bar -z cheese'
and it gives me a syntax error
That will work, but you can simplify it a bit like this:
args = parser.parse_args()
foo = args.one
bar = args.two
cheese = args.three
use args.__dict__
args.__dict__["one"]
args.__dict__["two"]
args.__dict__["three"]
The canonical way to get the values of the arguments that is suggested in the documentation is to use vars as you did and access the argument values by name:
argv = vars(args)
one = argv['one']
two = args['two']
three = argv['three']
can argparse be used to validate filename extensions for a filename cmd line parameter?
e.g. if i have a python script i run from the cmd line:
$ script.py file.csv
$ script.py file.tab
$ script.py file.txt
i would like argparse to accept the 1st two filename cmd line options but reject the 3rd
i know you can do something like this:
parser = argparse.ArgumentParser()
parser.add_argument("fn", choices=["csv","tab"])
args = parser.parse_args()
to specify two valid choices for a cmd line option
what i'd like is this:
parser.add_argument("fn", choices=["*.csv","*.tab"])
to specify two valid file extensions for the cmd line option. Unfortunately this doesn't work - is there a way to achieve this using argparse?
Sure -- you just need to specify an appropriate function as the type.
import argparse
import os.path
parser = argparse.ArgumentParser()
def file_choices(choices,fname):
ext = os.path.splitext(fname)[1][1:]
if ext not in choices:
parser.error("file doesn't end with one of {}".format(choices))
return fname
parser.add_argument('fn',type=lambda s:file_choices(("csv","tab"),s))
parser.parse_args()
demo:
temp $ python test.py test.csv
temp $ python test.py test.foo
usage: test.py [-h] fn
test.py: error: file doesn't end with one of ('csv', 'tab')
Here's a possibly more clean/general way to do it:
import argparse
import os.path
def CheckExt(choices):
class Act(argparse.Action):
def __call__(self,parser,namespace,fname,option_string=None):
ext = os.path.splitext(fname)[1][1:]
if ext not in choices:
option_string = '({})'.format(option_string) if option_string else ''
parser.error("file doesn't end with one of {}{}".format(choices,option_string))
else:
setattr(namespace,self.dest,fname)
return Act
parser = argparse.ArgumentParser()
parser.add_argument('fn',action=CheckExt({'csv','txt'}))
print parser.parse_args()
The downside here is that the code is getting a bit more complicated in some ways -- The upshot is that the interface gets a good bit cleaner when you actually go to format your arguments.
Define a custom function which takes the name as a string - split the extension off for comparison and just return the string if it's okay, otherwise raise the exception that argparse expects:
def valid_file(param):
base, ext = os.path.splitext(param)
if ext.lower() not in ('.csv', '.tab'):
raise argparse.ArgumentTypeError('File must have a csv or tab extension')
return param
And then use that function, such as:
parser = argparse.ArgumentParser()
parser.add_argument('filename', type=valid_file)
No. You can provide a container object to choices argument, or anything that supports the "in" operator. You can read more at pydocs
You can always check it yourself and provide feedback to the user though.
Can anyone spot why the following script is not printing the passed arguments?
import sys, getopt
def usage():
print 'Unknown arguments'
def main(argv):
try:
opts, args = getopt.getopt(argv,'fdmse:d',['files=','data-source=','mode=','start','end'])
except getopt.GetoptError:
usage()
sys.exit(999)
for opt, arg in opts:
# print opt,arg
if opt in('-f','--files'):
print 'files: ', arg #
if __name__ == "__main__":
main(sys.argv[1:])
When I run the script at the command line and pass the arguments -f=dummy.csv, usage() seems to be invoked instead - WHY?
BTW, I find the logic of the program flow a bit weird (I copied it from here). Normally, I would have thought that the logic will be implemented in the try branch, and then AFTER that comes the exception handler.
Is this (as pasted in the code above) the 'Pythonic' way to write try/catch blocks?
Did you get your answers?
One way to debug python exceptions is to move (or copy temporarily for debugging) the code out of the try block. You'll get a full trace.
And of course another way is to reduce the test case. Here I've reduced the problems to three lines, and tried the solution hinted at by #s.lott (using 'f:' in the getopts call), and also show at the end how calling with some different test data behaves:
$ cat x1.py
import sys, getopt
opts, args = getopt.getopt(sys.argv[1:],'fdmse:d',['files=','data-source=','mode=','start','end'])
print "opts=", opts, "args=", args
$ python x1.py -f=dummy.csv argblah
Traceback (most recent call last):
File "x1.py", line 2, in <module>
opts, args = getopt.getopt(sys.argv[1:],'fdmse:d',['files=','data-source=','mode=','start','end'])
File "/usr/lib/python2.6/getopt.py", line 91, in getopt
opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])
File "/usr/lib/python2.6/getopt.py", line 191, in do_shorts
if short_has_arg(opt, shortopts):
File "/usr/lib/python2.6/getopt.py", line 207, in short_has_arg
raise GetoptError('option -%s not recognized' % opt, opt)
getopt.GetoptError: option -= not recognized
$ sed 's/fdm/f:dm/' <x1.py >x2.py
$ diff x1.py x2.py
2c2
< opts, args = getopt.getopt(sys.argv[1:],'fdmse:d',['files=','data-source=','mode=','start','end'])
---
> opts, args = getopt.getopt(sys.argv[1:],'f:dmse:d',['files=','data-source=','mode=','start','end'])
$ python x2.py -f=dummy.csv argblah
opts= [('-f', '=dummy.csv')] args= ['argblah']
$ python x1.py -f dummy.csv argblah
opts= [('-f', '')] args= ['dummy.csv', 'argblah']
Normally, I would have thought that the logic will be implemented in the try branch
"Normally"? What does normally mean?
What is the program supposed to do? What exceptions make sense? What does the program do in response to the exceptions.
There's no "normally". Any more than there's a normal assignment statement or a normal function definition.
Your program does what makes sense to achieve the required end-state. There's no "normally".
Import and use argparse instead of getopt. It's much easier to use and has almost all you need for running from the command line, built into it.
An example,
parser = argparse.ArgumentParser(
description='Description of what the module does when run.')
parser.add_argument("-o", "--output", help='Path of log file.')
args = parser.parse_args()
As easy as that. You need to import argparse at the top of your file for this to work, of course.