handle errors in Python ArgumentParser
<p>I want to manually handle the situation where <code>parse_args()</code> throws an error in case of a unknown value for an argument. For example:
If I have the following python file called <code>script.py</code>:</p>

<pre><code>argp = argparse.ArgumentParser(description='example')
argp.add_argument('--compiler', choices=['default', 'clang3.4', 'clang3.5'])
args = argp.parse_args()
</code></pre>

<p>and I run the script with the following args <code>python script.py --compiler=foo</code> it throws the following error:</p>

<pre><code>error: argument --compiler: invalid choice: 'foo' (choose from 'default', 'clang3.4', 'clang3.5')
SystemExit: 2
</code></pre>

<p>What do I need to do in order to handle this behaviour myself instead of the script quitting itself? One idea is to subclass <code>argparse.ArgumentParser</code> and override <code>parse_args()</code> or just monkey patch the method but I was wondering if there's a better way that does not require overriding the standard library behaviour?</p>

<p>The whole point to defining choices is to make the parser complain about values that are not in the list.  But there are some alternatives:</p>

<ul>
<li><p>omit choices (include them in the help text if you want), and do your own testing after parsing. <code>argparse</code> doesn't have to do everything for you.  It's main purpose is to figure out what your user wants.</p></li>
<li><p>redefine the <code>parser.error</code> method (via subclassing is best) to redirect the error from <code>sys.exit</code>.  But you'll have to parse the error message to distinguish between this error and other ones that the parser might raise.</p></li>
<li><p>define a <code>type</code> function that checks for choices, and makes the default substitution.</p></li>
</ul>

<p>The parsing of the '--compiler' option goes something like this:</p>

<ul>
<li>grab the string argument after the <code>--compiler</code> flag</li>
<li>pass it through the <code>type</code> function.  Default is <code>lambda x:x</code>.  <code>int</code> converts it to integer, etc.  Raise <code>ValueError</code> is value is bad.</li>
<li>check the returned value against the <code>choices</code> list (if any)</li>
<li>use the <code>action</code> to add the value to the Namespace (default simply stores it).</li>
</ul>

<p>Error in any of these steps produces an <code>ArgumentError</code> which is trapped by the <code>parser.error</code> method and passed to a <code>parser.exit</code> method.</p>

<p>Since the <code>store_action</code> occurs after <code>type</code> and <code>choices</code> checking, a custom action won't bypass their errors.</p>

<p>Here's a possible <code>type</code> solution (not tested)</p>

<pre><code>def compile_choices(astr):
   if astr in ['default', 'clang3.4', 'clang3.5']:
       return astr
   else:
       return 'default'
   # could raise ValueError('bad value') if there are some strings you don't like

argp.add_argument('--compiler', type=compile_choices)
</code></pre>

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

<p>If <code>compile_choices</code> takes other arguments, such as the list of choices or the default, you'll need to wrap in some why that defines those values before parsing.</p>

<p>An example accepting a binary string representation:</p>

<pre><code>parser.add_argument('--binary', type=lambda x: int(x, base=2),
    help='integer in binary format', default='1010')
</code></pre>

<p>or</p>

<pre><code>parser.add_argument('--binary', type=functools.partial(int, base=2), default='1010')
</code></pre>

python python-2.7 python-3.x argparse