How do I get back the option string using argparse? - python

parser = argparse.ArgumentParser()
parser.add_argument("-p", "--pattern", help="Pattern file")
args = parser.parse_args()
Now is it possible to get back the string "--pattern" from args?
I need the string so that I can construct a cmd list to pass to Popen like Popen(['some_other_program', args.pattern.option_string, args.pattern], ...) without repeating it (and having to maintain it in two places) (Popen(['some_other_prog', '--pattern', args.pattern], ...)).
I need to create a wrapper for another program. Some of the args need to be passed to the wrapped program (via Popen) and some are required by the wrapper.
Is there a better method than the following example?
pass_1 = '--to-be-passed'
parser = argparse.ArgumentParser()
parser.add_argument("-p", pass_1, help="Pass me on")
parser.add_argument("-k", "--arg-for-wrapper")
args = parser.parse_args()
...
process = Popen(['wrapped_program', pass_1, args.pass_1], ...)
...
This method of keeping the args in variables is not very good as:
Maintaining short options along with long options becomes difficult.
Popen if called in another function requires passing these variables(or a dict of them) to the function. This seems redundant as args passed to it should be sufficient.

Add a dest to your add_argument call.
parser.add_argmument("p", "--pattern", dest="pattern", help="your help text")
args = parser.parse_args()
args = vars(args)
The you can reference the pattern with args["pattern"] .

There doesn't seem to be an easy way to get the original option strings from the result of a parser.parse_args(), but you can get them from the parser object. You just need to peek into its __dict__, in order to retrieve the parser settings after it's created. In your case you want the _option_string_actions field. Unfortunately this doesn't seem officially supported, as I couldn't find a ArgumentParser method dedicated to this, so YMMV. On Python 3:
Demo:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', '-f', type=int, default=1000, help='intensity of bar')
parser.add_argument('--bar', '-b', type=str, default='bla', help='whatever')
store_action_dict=vars(parser)['_option_string_actions']
print(store_action_dict.keys()) # dict_keys(['--help', '-b', '-f', '-h', '--foo', '--bar'])

The deleted answers and comments indicate there is some confusion as to what you want. So I'll add to that confusion.
Normally the parser does not record the option string. However it is provided to the Action __call__ method. So a custom Action class could save it. The FooAction custom class example in the argparse docs illustrates this.
If I define this action subclass:
In [324]: class PassThru(argparse._StoreAction):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, [values, option_string])
In [324]: p.add_argument('-o','--other',action=PassThru)
The option string is recorded along with the value ('-o' or '--other'):
In [322]: p.parse_args('-p test -o teseting'.split())
Out[322]: Namespace(other=['teseting', '-o'], pass_me_on='test')
In [323]: p.parse_args('-p test --other teseting'.split())
Out[323]: Namespace(other=['teseting', '--other'], pass_me_on='test')
Obviously the option_string and value could be recorded in a different order, in a dictionary, as seperate attributes in the Namespace, etc.
There are other ways of passing options to another program, particularly if the wrapping parser does not need to handle them itself.
argparse gets the arguments from sys.argv[1:], and does not change it. So even if your parser uses some of the arguments, you could pass that list on to popen (all or in part).
The argparse docs has an example, under nargs=REMAINDER, of parsing some arguments for itself, and collecting the rest to pass to another program. This is their example:
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo')
>>> parser.add_argument('command')
>>> parser.add_argument('args', nargs=argparse.REMAINDER)
>>> print(parser.parse_args('--foo B cmd --arg1 XX ZZ'.split()))
Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')
So you could call popen with something like
plist = ['wrapped_program']
plist.extend(args.args)
popen(plist, ...)
Using parse.parse_known_args can also be used to collect unparsed words into an 'extras' list. That section of the docs talks about passing those strings on to another program (just as you are doing). In contrast with the REMAINDER case, the extra stuff does not have to be last.
These work, of course, only if this parser doesn't need --pattern for itself. If it parses it, then it won't appear appear in the REMAINDER or extras. In that case you will have to add it back to the list that you give popen
I would tweak your parser thus:
pass_1 = 'passed' # without the -- or internal -
dpass_` = '--'+pass_
parser = argparse.ArgumentParser()
parser.add_argument("-p", dpass_1, help="Pass me on")
parser.add_argument("-k", "--arg-for-wrapper")
args = parser.parse_args()
process = Popen(['wrapped_program', dpass_1, getattr(args, pass_1)], ...)
another option:
parser = argparse.ArgumentParser()
pass_action = parser.add_argument("-p", '--pass-me-on', help="Pass me on")
parser.add_argument("-k", "--arg-for-wrapper")
args = parser.parse_args()
If you print pass_action (in a shell) you'll get something like:
_StoreAction(option_strings=['-p', '--pass-me-on'], dest='pass_me_on', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
So you could pull the --name and dest from that object, thus:
process = Popen(['wrapped_program', pass_action.option_strings[-1], getattr(args, pass_action.dest), ...], ...)
You have to look in sys.argv to see which option_string was used (the long, short or other). The parser does not record that anywhere.
Note '--pass-me-on' produced dest='pass_me_on'. The conversion of - to _ can complicate deriving one string from the other.
If you have a dest string, you have to use getattr to pull it from the args namespace, or use vars(args)[dest] (dictionary access).
Another issue. If --patten has nargs='+', its value will be a list, as opposed to a string. You'd have to careful when merging that into thepopen` argument list.

Related

python argparse different value if arg is provided in command line or absent

I want to use python's argparse to config the parameters input from command line. The behaviours for parsing the arguments can be:
None if the name is absent from command line.
Defaults to a value if the name is provided but left empty.
To the provided value.
Example code can be:
parser = argparse.ArgumentParser()
parser.add_argument("-generate-config", metavar="-g", nargs="?")
Parsing it:
>>> parser.parse_args(["-g"]) # leave it empty/didn't provide value
Namespace(generate_config=None)
>>> parser.parse_args(["-g", "config.txt"])
Namespace(generate_config='config.txt')
>>> parser.parse_args([]) # absent in the command line
Namespace(generate_config=None)
So leave it empty or don't provide the argument in the command line, both values are None. How can I set different behaviours/values for these two situations?
After searching stackflow, setting default to argparse.SUPPRESS in add_argument is of great help, as inspired by this answer.
parser = argparse.ArgumentParser()
parser.add_argument("-generate-config", metavar="-g", nargs="?", default=argparse.SUPPRESS)
So:
>>> args = parser.parse_args(["-g"]) # leave it empty/didn't provide value
>>> args
Namespace(generate_config=None)
>>> args = parser.parse_args([]) # the argument is empty
>>> args
Namespace()
>>> args = parser.parse_args(["-g", "parameters.txt"])
>>> args
Namespace(generate_config='parameters.txt')
Though not exactly as what I want, it still can be achieved by checking "generate_config" in args and args.generate_config is None.

How do I subclass argparse.Action to add a custom action?

I have a command line script I'm trying to run that inserts a default value into the Namespace if the value is absent, or takes the supplied argument as is if it's present.
So I want to do this:
myscript.py --merge
Would result in the argument parser Namespace looking like this:
Namespace(merge='--merge')
Else, if I call
myscript.py
Namespace should look like this:
Namespace(merge='DONTMERGE')
I think I need to subclass the argparse.Action class's __call__ method to perform a custom action as specified here: https://pymotw.com/2/argparse/ but I'm unable to figure out how to do this.
I thought something like this would do the trick:
class CustomAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if isinstance(self.values, None):
self.values = 'NOMERGE'
else:
self.values = '--nomerge'
setattr(namespace, self.dest, values)
Unfortunately, I'm not getting the expected result.
I think you just need a normal store_const argument.
parser.add_argument('--merge', action='store_const', const='MERGE', default='DONTMERGE')
If you call your script with --merge, the merge argument takes the value MERGE (specified above as const). Otherwise, the merge argument takes the value DONTMERGE (specified above as default).
See https://docs.python.org/2/library/argparse.html#action
The exact thing that you request is impossible to implement by subclassing Action. The reason is CustomAction instance is __call__'ed if and only if --merge is passed to the argument parser:
class CustomAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
print("Yay, I've been called")
setattr(namespace, self.dest, values)
p = argparse.ArgumentParser()
p.add_argument('--merge', nargs='?', action=CustomAction)
print(p.parse_args([]))
print(p.parse_args(['--merge']))
Namespace(merge=None)
Yay, I've been called
Namespace(merge=None)
Here I had to pass nargs='?', otherwise argparse requires an argument for --merge.
Many Actions override Action.__init__(...), but it has no access to parser's Namespace ¯\_(ツ)_/¯
If you want to go all the way with the subclasses, you'll probably have to subclass ArgumentParser itself and modify add_argument() method.
One could do some pretty cool and/or weird stuff with Actions, though:
class ReverseAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, ''.join(reversed(values)))
p = argparse.ArgumentParser()
p.add_argument('--string', action=ReverseAction)
print(p.parse_args(['--string', '12345']))
Namespace(string='54321')
(The only time I created a subclass of argparse.Action myself was to implement advanced validation)
As for your particular case, it looks to me like a job for a simple action='store_true':
>>> parser.add_argument('--merge', action='store_true')
_StoreTrueAction(option_strings=['--merge'], dest='merge', nargs=0, const=True,
default=False, type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args(['--merge'])
Namespace(merge=True)
>>> parser.parse_args([])
Namespace(merge=False)
…or even a BooleanOptionalAction (in Python >=3.9):
>>> parser.add_argument('--merge', action=argparse.BooleanOptionalAction, default=False)
BooleanOptionalAction(option_strings=['--merge', '--no-merge'], dest='merge', nargs=0,
const=None, default=False, type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args([])
Namespace(merge=False)
>>> parser.parse_args(['--no-merge'])
Namespace(merge=False)
>>> parser.parse_args(['--merge'])
Namespace(merge=True)
As usual, see argparse docs and/or source code =)

How to grab all defaults from `argparse`?

I have an argument parser like so:
def args_parser():
parser = argparse.ArgumentParser(description='myparser')
parser.add_argument('--k', type=int, default=100, help = 'numbers')
return parser
Now, all I want to do is just get the default mappings, like so:
args = args_parser()
And then I want args['k'] to return 100, since that is the default. How do I make this happen?
This should do that for you. I think you should be able to find this in the Python docs on ArgumentParser as well:
vars(args.parse_args())['k']
You need to call the .parse_args() function on your parser object in order to actually return the command-line arguments parsed and assigned to attributes as defined in your .add_argument() statement. So something like:
parser = args_parser()
args = parser.parse_args()
k_value = args.k
The 'args' variable returned by .parse_args() will have attributes assigned corresponding to the names you assign to it via the .add_argument() call, although if there is no default and the user does not provide a given argument its value will be 'None'.
def args_parser():
parser = argparse.ArgumentParser(description='myparser')
parser.add_argument('--k', type=int, default=100, help = 'numbers')
return parser
parser = args_parser():
args = parser.parse_args([])
print(args) # see all values
print(args.k) # see just the k attribute
parse_args() parses the sys.argv as provided in the commandline. parse_args([]) parses an empty list, same as if you had called the script without arguments. I regularly test a parser setup with calls like this.
With your argument definition the [] works fine and shows the default k value. With other defintions, [] might produce an error. For example if you included
parser.add_argument('infile', help='positional file name')
now a string is required. For a simple positional like that a default is not needed nor does it make sense. But other parameter combinations can make use of a default.
I could show you how to access the default as set in the add_argument command, but that involves some undocumented features. In addition there are several ways of defining defaults, so the topic can get complicated.
Anyways simply running the parse_args with an empty list is often enough.
You can also make it display the default in the help line with a little addition:
parser.add_argument('--k', type=int, default=100, help = 'numbers, %(default)s')
For example, in an interactive ipython session:
In [73]: parser=argparse.ArgumentParser()
In [74]: parser.add_argument('--k', type=int, default=100, help = 'numbers, %(default)s')
Out[74]: _StoreAction(option_strings=['--k'], dest='k', nargs=None,
const=None, default=100, type=<class 'int'>, choices=None,
help='numbers, %(default)s', metavar=None)
In [75]: parser.print_help()
usage: ipython3 [-h] [--k K]
optional arguments:
-h, --help show this help message and exit
--k K numbers, 100
In [76]: parser.parse_args([]) # displays args
Out[76]: Namespace(k=100)
In [78]: parser.parse_args(['--k','200'])
Out[78]: Namespace(k=200)

Why is there a difference when calling argparse.parse_args() or .parse_args(sys.argv)

I have created the following argument parser in my python code.
parser = argparse.ArgumentParser()
parser.add_argument('projectPath')
parser.add_argument('-project')
parser.add_argument('-release')
parser.add_argument('--test', default=False, action='store_true')
args = parser.parse_args()
and I'm executing my program the following way.
myProgram.py /path/to/file -project super --test
it works fine if I use the sysntax above with
args = parser.parse_args()
However if I take and use the sys.argv as input
args = parser.parse_args(sys.argv)
The parser is suddenly picky about the order of the arguments and I get the unrecognized argument error.
usage: fbu.py [-h] [-project PROJECT] [-release RELEASE] [--test] projectPath
fbu.py: error: unrecognized arguments: /path/to/file
As I can see from the error and also using the -h argument. The path argument must be last and the error makes sense in the last example.
But why does it not care about the order in the first example ?
EDIT: I'm using python version 3.4.3
sys.argv contains the script name as the first item, i.e. myProgram.py. That argument takes the spot of projectPath. Now there's one additional positional argument /path/to/file, which can't be matched to any arguments, hence the error.
Calling parse_args without arguments ArgumentParser is clever enough to omit the script name from being parsed. But when explicitly passing an array of arguments, it can't do that and will parse everything.
As you can see from looking at the source code for parse_known_args (which is called by parse_args):
if args is None:
# args default to the system args
args = _sys.argv[1:]
When you don't provide the arguments explicitly, Python removes the first item from .argv (which is the name of the script). If you pass the arguments manually, you must do this yourself:
parser.parse_args(sys.argv[1:])
This isn't explicitly covered in the documentation, but note that this section doesn't include a script name when calling parse_args manually:
Beyond sys.argv
Sometimes it may be useful to have an ArgumentParser parse arguments
other than those of sys.argv. This can be accomplished by passing a
list of strings to parse_args(). This is useful for testing at the
interactive prompt:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument(
... 'integers', metavar='int', type=int, choices=xrange(10),
... nargs='+', help='an integer in the range 0..9')
>>> parser.add_argument(
... '--sum', dest='accumulate', action='store_const', const=sum,
... default=max, help='sum the integers (default: find the max)')
>>> parser.parse_args(['1', '2', '3', '4'])
Namespace(accumulate=<built-in function max>, integers=[1, 2, 3, 4])
>>> parser.parse_args('1 2 3 4 --sum'.split())
Namespace(accumulate=<built-in function sum>, integers=[1, 2, 3, 4])
The advantage of passing the arguments manually is that it makes it easier to test the parsing functionality, as you can pass in a list of appropriate arguments rather than trying to patch sys.argv.

Python argparse: single-valued argument but allow specified multiple times on command line

In python argparse, is it possible to declare an argument which is just single-valued, instead a list, but allows being specified multiple times, the latest one overrides the earlier ones?
The use case is, I am writing a Python program that reads command line argument from a ~/.xxxrc file, where .xxxrc file has an command line argument per line. I want to allow user override the value in ~/.xxxrc file through command line. My plan is to implicitly adds an #~/.xxxrc to the beginning of argv before passing it to argparse, so that argparse will reads in the file. Now the problem turns into my question above.
Is it possible to achieve this effect? If not, is there any alternative solution to my use case.
The argparse module does that by default. Take a look at this example, where we specify the same flag twice, and the last one wins:
import argparse
parser = argparse.ArgumentParser(description='example')
parser.add_argument('-a', '--add')
options = parser.parse_args(['-a', 'foo', '-a', 'bar'])
print 'add = {}'.format(options.add) # output: add = bar
Yes, you can just create a custom action with an nargs='*' or nargs='+'.
Something like this should work:
class GetLastAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values:
setattr(namespace, self.dest, values[-1])
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', action=GetLastAction)

Categories

Resources