Parameter dependencies in Python - can't make it work
<p>I am trying to add a parameter dependency to my script. The idea is that <em>--clone</em> argument will require non-empty <em>--gituser</em>.</p>

<p>After perusing <a href="http://stackoverflow.com/questions/21879657/argparse-argument-dependency">this example</a>, I tried the following</p>

<pre><code>In [93]: class CloneAction(argparse.Action):
    ...:     def __call__(self, parser, namespace, _):
    ...:         if not namespace.git_user and namespace.clone:
    ...:             parser.error('"--clone" requires legal git user')
    ...:             
In [94]: parser = argparse.ArgumentParser()

In [95]: parser.add_argument('-g', '--gituser', dest='git_user', type=str, default='', action=CloneAction)
Out[95]: CloneAction(option_strings=['-g', '--gituser'], dest='git_user', nargs=None, const=None, default='', type=&lt;type 'str'&gt;, choices=None, help=None, metavar=None)

In [96]: parser.add_argument('--clone', action='store_true', default=False)
Out[96]: _StoreTrueAction(option_strings=['--clone'], dest='clone', nargs=0, const=True, default=False, type=None, choices=None, help=None, metavar=None)
</code></pre>

<p>Alas, it did not work</p>

<pre><code>In [97]: parser.parse_args(['--clone'])
Out[97]: Namespace(clone=True, git_user='')
</code></pre>

<p>What did I do wrong?</p>

<p>This kind of inter argument dependency is easier to implement <strong>after</strong> parsing.</p>

<pre><code>args = parser.parse_args()
if not namespace.git_user and namespace.clone:
    parser.error('"--clone" requires legal git user')
</code></pre>

<p>At that point, both <code>git_user</code> and <code>clone</code> have been parsed, and have their final values.</p>

<p>As you implemented it, the custom action is run only when there's a <code>--gituser</code> argument.  So I think it will raise the error when you give it <code>--gituser</code> without <code>--clone</code>.  </p>

<p>You could give <code>--clone</code> a similar custom action, but it would also have to handle the <code>store_true</code> details.  And what should happen with the <code>--clone --gituser value</code> sequence?  The <code>clone</code> action will be run before the <code>gituser</code> value has been parsed.  Tests like this run into some tough argument order problems.</p>

<p>A couple of other issues:</p>

<ul>
<li><p>your custom action does not store any value, regardless of error not.  It's better to customize the <code>store</code> subclass.</p></li>
<li><p>custom actions should raise <code>argparse.ArgumentError</code> rather than call <code>parser.error</code> directly.  </p></li>
</ul>

<p>The unittest file, <code>test/test_argparse.py</code> has an example of custom actions with mutual tests like this.  But it's just a toy, verifying that such code is allowed.</p>

<p>==================</p>

<p>You could, in theory, implement a <code>--clone</code> action that sets the <code>required</code> attribute of the <code>--gituser</code> Action.  That way, if <code>--gituser</code> is not used, the final <code>required</code> actions test of <code>parse_args</code> will raise an error.  But that requires saving a reference to the Action displayed in <code>out[95]</code> (or finding that in the <code>parse._actions</code> list.  Feasible but messy.</p>

<p>===================</p>

<p>Here's an example of a pair of interacting custom action classes from <code>test/test_argparse.py</code>.</p>

<pre><code>class OptionalAction(argparse.Action):

    def __call__(self, parser, namespace, value, option_string=None):
        try:
            # check destination and option string
            assert self.dest == 'spam', 'dest: %s' % self.dest
            assert option_string == '-s', 'flag: %s' % option_string
            # when option is before argument, badger=2, and when
            # option is after argument, badger=&lt;whatever was set&gt;
            expected_ns = NS(spam=0.25)
            if value in [0.125, 0.625]:
                expected_ns.badger = 2
            elif value in [2.0]:
                expected_ns.badger = 84
            else:
                raise AssertionError('value: %s' % value)
            assert expected_ns == namespace, ('expected %s, got %s' %
                                              (expected_ns, namespace))
        except AssertionError:
            e = sys.exc_info()[1]
            raise ArgumentParserError('opt_action failed: %s' % e)
        setattr(namespace, 'spam', value)
</code></pre>

<p><code>NS</code> is a shorthand for <code>argparse.Namespace</code>.</p>

<pre><code>class PositionalAction(argparse.Action):

    def __call__(self, parser, namespace, value, option_string=None):
        try:
            assert option_string is None, ('option_string: %s' %
                                           option_string)
            # check destination
            assert self.dest == 'badger', 'dest: %s' % self.dest
            # when argument is before option, spam=0.25, and when
            # option is after argument, spam=&lt;whatever was set&gt;
            expected_ns = NS(badger=2)
            if value in [42, 84]:
                expected_ns.spam = 0.25
            elif value in [1]:
                expected_ns.spam = 0.625
            elif value in [2]:
                expected_ns.spam = 0.125
            else:
                raise AssertionError('value: %s' % value)
            assert expected_ns == namespace, ('expected %s, got %s' %
                                              (expected_ns, namespace))
        except AssertionError:
            e = sys.exc_info()[1]
            raise ArgumentParserError('arg_action failed: %s' % e)
        setattr(namespace, 'badger', value)
</code></pre>

<p>They are used in</p>

<pre><code>parser = argparse.ArgumentParser()
parser.add_argument('-s', dest='spam', action=OptionalAction,
        type=float, default=0.25)
parser.add_arugment('badger', action=PositionalAction,
        type=int, nargs='?', default=2)
</code></pre>

<p>And supposed to work with:</p>

<pre><code>'-s0.125' producing: NS(spam=0.125, badger=2)),
'42',                NS(spam=0.25, badger=42)),
'-s 0.625 1',        NS(spam=0.625, badger=1)),
'84 -s2',            NS(spam=2.0, badger=84)),
</code></pre>

<p>This is an example of the kind of cross checking that can be done.  But I'll repeat that generally interactions are best handled after parsing, not during.</p>

<p>As to the implementation question - if the user does not give you <code>--gituser</code>, your custom Action is never called.  The <code>Action.__call__</code> of an <code>optional</code> is only used when that argument is used.  <code>positionals</code> are always used, but not <code>optionals</code>.</p>

python argparse