Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using argparse, should validation and initialization happen in custom types or actions?

Python 2.7's argparse gives you two extension points where you can control how your command-line arguments are parsed: type functions and action classes.

Going from the built-in types and actions, the best practice seems to be that type functions should contain the validation/initialization code and actions should be concerned with storing values into namespace. The problem with this approach is when you have type-checking code that has side effects. Consider this simple example:

from argparse import ArgumentParser, FileType
argp = ArgumentParser()
argp.add_argument('-o', type=FileType('w'), default='myprog.out')
argp.parse_args(['-o', 'debug.out'])

If you run this, you find python will open two files on the system, myprog.out and debug.out. It would make more sense to only open debug.out when the user doesn't supply the -o argument.

Poking around a bit, it seems that argparse will only call your type function on passed arguments or default arguments of type str. This is unfortunate if your type-checker has side-effects as it will be called on the default even if a value was passed. So for initialization with side-effects, maybe it would be better to do it in the action. The problem with this is that the action won't be called if you supply a default value!

Consider the following code:

from argparse import ArgumentParser, Action

def mytype(arg):
    print 'checking type for ' + repr(arg)
    return arg

class OutputFileAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        print 'running action for ' + repr(values)
        try:
            outstream = open(values, 'w')
        except IOError as e:
            raise ArgumentError('error opening file ' + values)
        setattr(namespace, self.dest, outstream)

argp = ArgumentParser()
argp.add_argument('-o', type=mytype, action=OutputFileAction, default='myprog.out')

Now try to use it:

>>> argp.parse_args([])
checking type for 'myprog.out'
Namespace(o='myprog.out')
>>> argp.parse_args(['-o', 'debug.out'])
checking type for 'myprog.out'
checking type for 'debug.out'
running action for 'debug.out'
Namespace(o=<open file 'debug.out', mode 'w' at 0x2b7fced07300>)

Who ordered that behavior? Is there a sane way to have default values behave exactly as if they were passed in by the user? Or to not typecheck defaults when values are supplied?

like image 552
Nick Avatar asked Sep 13 '12 00:09

Nick


People also ask

What is action in Argparse?

action defines how to handle command-line arguments: store it as a constant, append into a list, store a boolean value etc. There are several built-in actions available, plus it's easy to write a custom one.

Which of the following parameters is set to specify that passing an argument is mandatory while adding the argument to a created parser?

required is a parameter of the ArugmentParser object's function add_argument() . By default, the arguments of type -f or --foo are optional and can be omitted. If a user is required to make an argument, they can set the keyword argument required to True .

How does Argparse work?

The argparse module makes it easy to write user-friendly command-line interfaces. It parses the defined arguments from the sys. argv . The argparse module also automatically generates help and usage messages, and issues errors when users give the program invalid arguments.

What is action Store_true in Argparse?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present. The source for this behavior is succinct and clear: http://hg.python.org/cpython/file/2.7/Lib/argparse.py#l861.


1 Answers

As far as I am aware, there is no "sensible" way to do this. Of course, it is trivial to leave the type conversion off and then postprocess the Namespace returned from parse_args:

args = argp.parse_args()
args.o = open(args.o,'w')

but I suppose that isn't what you're looking for.

like image 140
mgilson Avatar answered Oct 05 '22 13:10

mgilson