I'm trying to get back the version string I defined in argparse for use in logging.
I'm using a typical setup along the lines of:
__version__ = "0.1"
parser = argparse.ArgumentParser()
parser.add_argument('--version', '-V', action='version', version="%(prog)s " + __version__)
args = parser.parse_args()
When I print parser.version() or parser.print_version() or parser.format_version() I get None. One solution is to call parser.parse_args(['-V']) but that also terminates the execution of the program. I know I can just re-create the string and pass it to the logger, but I thought there must be a way to get this from argparse. I'm using python v2.7.5 and argparse v1.1.
There is no public API to get that information. The parser.version attribute is deprecated. You'd have to find the argparse._VersionAction object and get it from there:
version_action = next((action for action in parser._get_optional_actions()
if isinstance(action, argparse._VersionAction)), None)
print version_action.version if version_action else 'unknown'
This uses private methods that are subject to change, and all you get is the exact same string you gave to argparse:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--version', '-V', action='version', version="%(prog)s " + __version__)
_VersionAction(option_strings=['--version', '-V'], dest='version', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help="show program's version number and exit", metavar=None)
>>> version_action = next((action for action in parser._get_optional_actions() if isinstance(action, argparse._VersionAction)), None)
>>> print version_action.version if version_action else 'unknown'
%(prog)s 0.1
Much easier just to store that value somewhere else too, then pass it to argparse and use the value directly.
format_version (and print_version which uses format_version) displays the parser.version attribute, which you can set with the version parameter. But as Martijn wrote, that approach to showing a version is depricated. You'll get a warning message.
That action='version' argument takes a different route. It has its own version parameter (but using parser.version as a backup). When triggered by the -v argument string, it displays that version info and calls system exit.
You could still call -v, and avoid the system exit with a try block:
try:
p.parse_args(['-V'])
except SystemExit:
pass
That's handy for testing, but not something you want in production. Plus it traps other exits like help and errors.
As with any argument, you can save a link to the Action, and display, use, or even modify its attributes.
version_action = parser.add_argument('--version', '-V', action='version',
version="%(prog)s " + __version__)
print version_action
print version_action.version
assert isinstance(version_action, argparse._VersionAction)
Functionally this is the same as Martijn's search of the parser._get_optional_actions(), but simpler.
You could replicate the action of the _VersionAction.__call__, without the system exit, with:
def foo(action, parser):
formatter = parser._get_formatter()
formatter.add_text(action.version)
return formatter.format_help()
foo(version_action, parser)
Though in this case all it does is fill in the %(prog)s string.
http://bugs.python.org/issue9399 'Provide a 'print' action for argparse', discusses adding a 'print' or 'write' action type. It would behave like the 'version' one, but without the system exit, and possibly more control over formatting and print destination.
No, there must not be a way to get it from argparse. Why would there be? You tell argparse what the version number is, not the other way around.
And you don't need to "recreate" the string. Just create it once, and then pass it into argparse.
I'm sure it's available somehow, from some attribute on argparse, but getting it from there really makes no sense, and requires you to use argparse internals, which may change in the future.
Related
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--selection', '-s')
parser.add_argument('--choice', '-c', default = argparse.SUPPRESS)
args = parser.parse_args()
def main(selection, choice):
print(selection)
print(choice)
if __name__=='__main__':
main(args.selection, args.choice)
The example provided is just to provide something simple and short that accurately articulates the actual problem I am facing in my project. My goal is to be able to ignore an argument within the code body when it is NOT typed into the terminal. I would like to be able to do this through passing the argument as a parameter for a function. I based my code off of searching 'suppress' in the following link: https://docs.python.org/3/library/argparse.html
When I run the code as is with the terminal input looking like so: python3 stackquestion.py -s cheese, I receive the following error on the line where the function is called:
AttributeError: 'Namespace' object has no attribute 'choice'
I've tried adding the following parameter into parser like so:
parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
I've also tried the above with
parser.add_argument('--choice', '-c')
But I get the same issue on the same line.
#Barmar answered this question in the comments. Using 'default = None' in parser.add_argument works fine; The code runs without any errors. I selected the anser from #BorrajaX because it's a simple solution to my problem.
According to the docs:
Providing default=argparse.SUPPRESS causes no attribute to be added if the command-line argument was not present:
But you're still assuming it will be there by using it in the call to main:
main(args.selection, args.choice)
A suppressed argument won't be there (i.e. there won't be an args.choice in the arguments) unless the caller specifically called your script adding --choice="something". If this doesn't happen, args.choice doesn't exist.
If you really want to use SUPPRESS, you're going to have to check whether the argument is in the args Namespace by doing if 'choice' in args: and operate accordingly.
Another option (probably more common) can be using a specific... thing (normally the value None, which is what argparse uses by default, anyway) to be used as a default, and if args.choice is None, then assume it hasn't been provided by the user.
Maybe you could look at this the other way around: You want to ensure selection is provided and leave choice as optional?
You can try to set up the arguments like this:
parser = argparse.ArgumentParser()
parser.add_argument('--selection', '-s', required=True)
parser.add_argument('--choice', '-c')
args = parser.parse_args()
if __name__ == '__main__':
if args.choice is None:
print("No choice provided")
else:
print(f"Oh, the user provided choice and it's: {args.choice}")
print(f"And selection HAS TO BE THERE, right? {args.selection}")
I want to use the argparse library because of its flexibility, but I am having trouble disabling the default help dialogue to show a custom one from a text file. All I want to do is display the text from the text file when the "-h" or "--help" option is passed. Here is an example of how I am trying this:
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("file", type=str, nargs='+')
parser.add_argument("-xmin", type=float)
parser.add_argument("-xmax", type=float)
parser.add_argument("-ymin", type=float)
parser.add_argument("-ymax", type=float)
parser.add_argument("-h", "--help", action="store_true")
args = parser.parse_args()
if args.help is True:
print isip_get_help()
exit(-1)
But it still outputs:
nedc_[1]: python isip_plot_det.py -h
usage: isip_plot_det.py [-xmin XMIN] [-xmax XMAX] [-ymin YMIN] [-ymax YMAX]
[-h]
file [file ...]
isip_plot_det.py: error: too few arguments
Any ideas?
What you are getting is an error message, not the help (i.e. it's not produced by your -h).
isip_plot_det.py: error: too few arguments
The error message shows the usage part of the normal help. You can change that with a usage parameter:
parser = ArgumentParser(usage = 'my custom usage line')
You can also test the usage display with
parser.print_usage()
or
astr = parser.format_usage()
to get a printable string.
The normal help argument uses a special help action class. Its call method is:
def __call__(self, parser, namespace, values, option_string=None):
parser.print_help()
parser.exit()
Notice that it displays the help with parser.print_help(), and then exits. That occurs as soon as it parses the -h string. That way it doesn't produce any errors like the too few arguments or unrecognized arguments (which are produced at the end of parsing).
So another way of customizing the help is to subclass ArgumentParser, and define your own print_help method. You can also customize the exit and error methods.
The default print_help is:
def print_help(self, file=None):
if file is None:
file = sys.stdout
self._print_message(self.format_help(), file)
You could customize format_help instead.
class MyParser(argparse.ArgumentParser):
def format_help(self):
return 'my custom help message\n second line\n\n'
Sample usage:
In [104]: parser=MyParser(usage='custom usage')
In [105]: parser.parse_args(['-h'])
my custom help message
second line
...
In [106]: parser.parse_args(['unknown'])
usage: custom usage
ipython3: error: unrecognized arguments: unknown
...
The failure is caused by the missing required argument file. The reason why action would not be subjected to this validation requirement is simply because they are executed first, i,e, args.help will be set to True. However, once the parser completes parsing the arguments it would have triggered a sys.exit due to validation failure, and this ends with printing out the default usage and your code (that prints your desired help message) will simply never be executed (you can try calling your program as is with a -h file arguments and your help message should print).
You can either add your custom action using parser.register (not officially supported as it's considered private API, but it works - read argparse.py to see how it all works), or alternatively subclass ArgumentParser and override the print_help method to call isip_get_help().
You could try something like this:
class MyArgumentParser(argparse.ArgumentParser):
def print_help(self, file=None):
print(isip_get_help())
exit(-1)
parser = MyArgumentParser()
...
Leave the default add_help in, but with print_help overridden to call your custom help it should work.
I am now starting exploring Python, and was testing how arguments can be passed to a script with "argparse".
The way I wrote a sample script was as following, where arguments passed through flags -i and -o are compulsory and flag -u is optional:
#!/usr/bin/python
import sys
import argparse
## set usage options and define arguments
usage = "usage: %prog [options]"
parser = argparse.ArgumentParser(usage)
parser.add_argument("-i", action="store", dest="input", help="input file")
parser.add_argument("-o", action="store", dest="output", help="output file")
parser.add_argument("-u", action="store_true", dest="isunfolded", default=False, help="optional flag")
args = parser.parse_args()
print len(sys.argv)
if len(sys.argv) < 2:
# parser.print_help()
print 'Incorrect number of params'
exit()
else:
print "Correct number of params: ", len(sys.argv)
Running this script:
> ./test-args.py -i a -o b
prints:
5
Correct number of params: 5
I understand the printing statement in the if conditional (5 is higher than 2), however, after reading the argparse documentation (https://docs.python.org/3/library/argparse.html) I still don't quite understand why -i and -o flags are counted as arguments. This behaviour seems to be quite different from e.g. perl Getopt::Std, which I'm more used to.
So, the question is what is the best way of parsing arguments in Python and to evaluate the presence of mandatory arguments (without using required=True)
It gives you 5 because sys.argv contains the raw input passed to python as arguments (the script name and 4 arguments).
You can see argparse as an abstraction for this, so once you use it, you can forget about sys.argv. In most cases it is better not to mix these two methods.
argparse its a nice way to handle arguments, I don't quite get why you don't want to use the required option when that's exactly the way to go. Another alternative is to parse the sys.argv yourself (regex maybe?) and drop argparse altogether.
There's a Python getopt which probably is similar to the Perl one (assuming both are modelled after the C/Unix version).
https://docs.python.org/2/library/getopt.html
In your code, sys.argv is a list of strings from the command line (as interpreted by the shell and interpreter). It is the raw input for any of the parsers ('getopt', 'optparse', 'argparse'). And it is available for your parsing as well. When learning it is a good idea to include a
print sys.argv
line. parser.parse_args() uses this list. sys.argv[0] is used as prog attribute (in the default usage), while sys.argv[1:] (the rest) is parsed according to the rules you define with add_argument. For testing I often like to use parse_args with a custom list of strings, e.g.
print parser.parse_args(['-i', 'input', '-o', 'output', '-u'])
With your definition I'd expect to see something like:
Namespace(input='input', output='output', isunfolded=True)
The parser returns an object (type argparse.Namespace), which has attributes defined by your arguments. Values are usually accessed with expressions like args.input, args.isunfolded. The docs also show how you easily express this as a dictionary.
By long standing UNIX conventions, arguments flagged by strings like '-i' are options, that is they are optional. argparse generalizes this concept by letting you specify a required=True parameter.
Other arguments are positionals. They are interpreted according to their order. And as such they are normally required. What argparse adds is the ability to define those positionals, such as type, nargs, etc. With nargs='?' they are optional. Many of the nargs values are similar to regular expression characters (e.g. +?*). In fact argparse uses a form a regular expression parsing to allocate strings among arguments.
I'd refine your arguments thus (taking advantage of various defaults)
a1 = parser.add_argument("-i", "--input", help="input file") # 'store' is the default
a2 = parser.add_argument("-o", "--output",help="output file") # use the --output as dest
a3 = parser.add_argument("-u", "--isunfolded", action="store_true", help="optional flag")
If input and output were required, I could change them to:
parser.add_argument("input", help="input file") # 'store' is the default
parser.add_argument("output",help="output file") # use the --output as dest
parser.add_argument("-u", "--isunfolded", action="store_true", help="optional flag")
Now input and output are positional arguments, as in test.py -u inputfile outputfile
By using a1 = parser... I can look at the object produced by this statement.
print a1
produces
_StoreAction(option_strings=['-i', '--input'], dest='input', nargs=None, const=None,
default=None, type=None, choices=None, help='input file', metavar=None)
This tells me that a1 is a _StoreAction object (a subclass of argparse.Action). It also displays a number (not all) of its attributes, ones that define its action. A positional, on the other hand, has values like these:
a2 = p.add_argument("output", help="output file")
_StoreAction(option_strings=[], dest='output', nargs=None, const=None,
default=None, type=None, choices=None, help='output file', metavar=None)
It may also be instructive to look at a1.required and a2.required, which are respectively False and True. required is an Action attribute that is not routinely displayed, but is, never the less accessible.
I've pulled all these test values from a parser defined in an interactive shell (Ipython). It's a great way to explore Python and modules like argparse.
After reading other related posts it seems that the best way to do this is as was suggested by #Rufflewind and inspect the args itself:
if not args.input or not args.output:
print 'Incorrect number of params'
exit()
else:
print "Correct number of params"
I'm trying to create a command line interface for the dft.ba URL shortener using python's argparse and a function in .profile which calls my python script. This is the business end of the code in my python script:
parser = argparse.ArgumentParser(description='Shortens URLs with dft.ba')
parser.add_argument('LongURL',help='Custom string at the end of the shortened URL')
parser.add_argument('--source','-s',help='Custom string at the end of the shortened URL')
parser.add_argument('-c','--copy', action="store_true", default=False, help='Copy the shortened URL to the clipboard')
parser.add_argument('-q','--quiet', action="store_true", default=False, help="Execute without printing")
args = parser.parse_args()
target_url=args.LongURL
source=args.source
print source
query_params={'auth':'ip','target':target_url,'format':'json','src':source}
shorten='http://dft.ba/api/shorten'
response=requests.get(shorten,params=query_params)
data=json.loads(response.content)
shortened_url=data['response']['URL']
print data
print args.quiet
print args.copy
if data['error']:
print 'Error:',data['errorinfo']['extended']
elif not args.copy:
if args.quiet:
print "Error: Can't execute quietly without copy flag"
print 'Link:',shortened_url
else:
if not args.quiet:
print 'Link:',shortened_url
print 'Link copied to clipboard'
setClipboardData(shortened_url)
and then in .profile I have this:
dftba(){
cd ~/documents/scripts
python dftba.py "$1"
}
running dftba SomeURL will spit a shortened URL back at me, but none of the options work.When I try to use -s SomeSource before the LongURL, it gives error: argument --source/-s: expected one argument, when used afterwards it does nothing and when omitted gives error: too few arguments. -c and -q give error: too few arguments for some reason. The copy to clipboard function I'm using works perfectly fine if I force copying, however.
I'm very much feeling my way through this, so I'm sorry if I've made some glaringly obvious mistake. I get the feeling the problem's in my bash script, I just don't know where.
Any help would be greatly appreciated. Thank you.
Lets just focus on what the parser does
parser = argparse.ArgumentParser(description='Shortens URLs with dft.ba')
parser.add_argument('LongURL',help='Custom string at the end of the shortened URL')
parser.add_argument('--source','-s',help='Custom string at the end of the shortened URL')
parser.add_argument('-c','--copy', action="store_true", default=False, help='Copy the shortened URL to the clipboard')
parser.add_argument('-q','--quiet', action="store_true", default=False, help="Execute without printing")
args = parser.parse_args()
print args # add to debug the `argparse` behavior
LongURL is a positional argument that is always required. If missing you'll get the 'too few arguments' error message.
source is optional, but when provided must include an argument. If not given args.source is None. As written the source argument must be given in ADDITION to the LongURL one.
Both args.copy and args.quiet are booleans; default value is False; and True if given. (the default=False parameter is not needed.)
I haven't tried to work through the logic using copy and quiet. That won't come into play if there are problems earlier with LongURL and source.
Compare these samples:
In [38]: parser.parse_args('one'.split())
Out[38]: Namespace(LongURL='one', copy=False, quiet=False, source=None)
In [41]: parser.parse_args('-s one two -c -q'.split())
Out[41]: Namespace(LongURL='two', copy=True, quiet=True, source='one')
It may also help to look at what parse_args is parsing: sys.argv[1:] (if you have doubts about what you are getting from .profile).
With Perl's Getopt::Long you can easily define command-line options that take a variable number of arguments:
foo.pl --files a.txt --verbose
foo.pl --files a.txt b.txt c.txt --verbose
Is there a way to do this directly with Python's optparse module? As far as I can tell, the nargs option attribute can be used to specify a fixed number of option arguments, and I have not seen other alternatives in the documentation.
This took me a little while to figure out, but you can use the callback action to your options to get this done. Checkout how I grab an arbitrary number of args to the "--file" flag in this example.
from optparse import OptionParser,
def cb(option, opt_str, value, parser):
args=[]
for arg in parser.rargs:
if arg[0] != "-":
args.append(arg)
else:
del parser.rargs[:len(args)]
break
if getattr(parser.values, option.dest):
args.extend(getattr(parser.values, option.dest))
setattr(parser.values, option.dest, args)
parser=OptionParser()
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose",
help="be vewwy quiet (I'm hunting wabbits)")
parser.add_option("-f", "--filename",
action="callback", callback=cb, dest="file")
(options, args) = parser.parse_args()
print options.file
print args
Only side effect is that you get your args in a list instead of tuple. But that could be easily fixed, for my particular use case a list is desirable.
My mistake: just found this Callback Example 6.
I believe optparse does not support what you require (not directly -- as you noticed, you can do it if you're willing to do all the extra work of a callback!-). You could also do it most simply with the third-party extension argparse, which does support variable numbers of arguments (and also adds several other handy bits of functionality).
This URL documents argparse's add_argument -- passing nargs='*' lets the option take zero or more arguments, '+' lets it take one or more arguments, etc.
Wouldn't you be better off with this?
foo.pl --files a.txt,b.txt,c.txt --verbose
I recently has this issue myself: I was on Python 2.6 and needed an option to take a variable number of arguments. I tried to use Dave's solution but found that it wouldn't work without also explicitly setting nargs to 0.
def arg_list(option, opt_str, value, parser):
args = set()
for arg in parser.rargs:
if arg[0] == '-':
break
args.add(arg)
parser.rargs.pop(0)
setattr(parser.values, option.dest, args)
parser=OptionParser()
parser.disable_interspersed_args()
parser.add_option("-f", "--filename", action="callback", callback=arg_list,
dest="file", nargs=0)
(options, args) = parser.parse_args()
The problem was that, by default, a new option added by add_options is assumed to have nargs = 1 and when nargs > 0 OptionParser will pop items off rargs and assign them to value before any callbacks are called. Thus, for options that do not specify nargs, rargs will always be off by one by the time your callback is called.
This callback is can be used for any number of options, just have callback_args be the function to be called instead of setattr.
Here's one way: Take the fileLst generating string in as a string and then use http://docs.python.org/2/library/glob.html to do the expansion ugh this might not work without escaping the *
Actually, got a better way:
python myprog.py -V -l 1000 /home/dominic/radar/*.json <- If this is your command line
parser, opts = parse_args()
inFileLst = parser.largs