The truth is rarely pure and never simple

python argparse: graceful parameter parsing

Although usually it is helpful to define longer but explaining command line parameters, it will be tedious for users who work with this application very often to type out each and every parameter. Here is an example how to solve the problem for the python argparse module and choice options. This way, the program accepts any value that consists of the starting letters of any valid choice – as long as these starting letters are unique to all possible options. Here is one example for the choices ‘foo’, ‘bar’, and ‘foobar’.

User input Result
foo foo choice selected
fo Error, as both foo and foobar begin with ‘fo’
b bar choice selected

So now for argparse. Here is the code for a positional argument called ‘mode’

modes = ['foo', 'foobar', 'bar']
parser.add_argument(
    'mode', 
    type=str, 
    help='Nobody expects the spammish repetition.', 
    choices=forgive_choice_list(modes), 
    action=forgive_choice_action
    )

The choices parameter is common for this setup. Unfortunately, argparse checks for validity of the value given by the user. In this case, we have to override the behaviour of the in keyword. This is done by the forgive_choice_list class (no, I don’t like uppercase names for classes).

class forgive_choice_list(object):
    def __init__(self, iterable):
        self._choices = [str(i) for i in iterable]

    def __contains__(self, item):
        return bool(self.expand(item))

    def __iter__(self):
        return iter(self._choices)

    def expand(self, item):
        if item in self._choices:
            return item
        candidates = [x for x in self._choices if x.startswith(item)]
        if len(candidates) == 1:
            return candidates[0]
        else:
            return None

This makes argparse accept the shortcuts as desired. Now we have to fix the problem that the actual values stored for the ‘mode’ option is the abbreviation, as well. This is done by the following code:

class forgive_choice_action(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, self.choices.expand(values))

Leave a comment

Your email address will not be published.