Cause Python's argparse to execute action for default

argparse, python

Solution

`argparse` does not use the `action` when applying the `default`. It just uses `setattr`. It may use the `type` if the default is a string. But you can invoke the `action` directly.

Here I use a custom action class borrowed from the documentation. In the first `parse_args` nothing happens. Then I create a new `namespace`, and invoke the action on the default. Then I pass that namespace to `parse_args`. To understand this, you many need to import it into an interactive shell, and examine the attributes of the namespace and action.

# sample custom action from docs
class FooAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        print('Setting: %r %r %r' % (namespace, values, option_string))
        setattr(namespace, self.dest, 'action:'+values)
p = argparse.ArgumentParser()
a1 = p.add_argument('--foo', action=FooAction, default='default')
print 'action:',a1
print p.parse_args([])

ns = argparse.Namespace()
a1(p, ns, a1.default, 'no string') # call action
print p.parse_args([],ns)
print p.parse_args(['--foo','1'],ns)

which produces:

action: FooAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default='default', type=None, choices=None, help=None, metavar=None)
Namespace(foo='default')
Setting: Namespace() 'default' 'no string'
Namespace(foo='action:default')
Setting: Namespace(foo='action:default') '1' '--foo'
Namespace(foo='action:1')

I tailored the output to highlight when the action is being used.

Here's a way of performing a special action on an argument that isn't given on the command line (or given with a value == to the default). It's a simplification of the class given in https://stackoverflow.com/a/24638908/901925.

class Parser1:
    def __init__(self, desc):
        self.parser = argparse.ArgumentParser(description=desc)
        self.actions = []

    def milestone(self, help_='milestone for latest release.', default=None):
        action = self.parser.add_argument('-m', '--milestone', help=help_, default=default)
        self.actions.append(action)
        return self

    def parse(self):
        args = self.parser.parse_args()
        for a in self.actions:
            if getattr(args, a.dest) == a.default:
                print 'Please specify', a.dest
                values = raw_input('>')
                setattr(args, a.dest, values)
        return args

print Parser1('desc').milestone(default='PROMPT').parse()

The prompting is done after `parse_args`. I don't see any reason to call `parse_args` again.

Problem

I am using argparse's action to add various data to a class. I would like to use that action on the default value if that arg is not provided at the command line. Is this possible? Thanks!

Original source

Related problems