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?
As far as I am aware, there is no “sensible” way to do this. Of course, it is trivial to leave the
typeconversion off and then postprocess theNamespacereturned fromparse_args:but I suppose that isn’t what you’re looking for.