Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python argparse - optional append argument with choices

I have a script where I ask the user for a list of pre-defined actions to perform. I also want the ability to assume a particular list of actions when the user doesn't define anything. however, it seems like trying to do both of these together is impossible.

when the user gives no arguments, they receive an error that the default choice is invalid

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args([])
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]]
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock')

and when they do define a set of actions, the resultant namespace has the user's actions appended to the default, rather than replacing the default

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args(['lock'])
args
>>> Namespace(action=[['dump', 'clear'], ['dump']])
like image 223
Steve Avatar asked Dec 15 '11 21:12

Steve


2 Answers

What you need can be done using a customized argparse.Action as in the following example:

import argparse

parser = argparse.ArgumentParser()

class DefaultListAction(argparse.Action):
    CHOICES = ['clear','copy','dump','lock']
    def __call__(self, parser, namespace, values, option_string=None):
        if values:
            for value in values:
                if value not in self.CHOICES:
                    message = ("invalid choice: {0!r} (choose from {1})"
                               .format(value,
                                       ', '.join([repr(action)
                                                  for action in self.CHOICES])))

                    raise argparse.ArgumentError(self, message)
            setattr(namespace, self.dest, values)

parser.add_argument('actions', nargs='*', action=DefaultListAction,
                    default = ['dump', 'clear'],
                    metavar='ACTION')

print parser.parse_args([])
print parser.parse_args(['lock'])

The output of the script is:

$ python test.py 
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])
like image 184
jcollado Avatar answered Oct 10 '22 14:10

jcollado


In the documentation (http://docs.python.org/dev/library/argparse.html#default), it is said :

For positional arguments with nargs equal to ? or *, the default value is used when no command-line argument was present.

Then, if we do :

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts, default='clear')    
print p.parse_args([])

We get what we expect

Namespace(action='clear')

The problem is when you put a list as a default. But I've seen it in the doc,

parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')

So, I don't know :-(

Anyhow, here is a workaround that does the job you want :

import sys, argparse
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts)
args = ['dump', 'clear'] # I set the default here ... 
if sys.argv[1:]:
    args = p.parse_args()
print args
like image 40
n1r3 Avatar answered Oct 10 '22 15:10

n1r3