15

I want to generate custom error messages for particular usage errors in my command-line program that uses the argparse library. I know I can override the general presentation of the error by subclassing argparse.ArgumentParser:

class HelpParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        sys.exit(2)

parser = HelpParser(... ...)
args = parser.parse_args()

But when my error method is called, message has already been formatted by the library. For example,

> python prog.py old stuff

usage: prog [-h] {hot,cold,rain,snow} ...
prog: error: argument subparser: invalid choice: 'old' (choose from u'hot', u'cold', u'rain', u'snow')

How can I change how the stuff after error: is presented, for instance to

usage: prog [-h] {hot,cold,rain,snow} ...
error: 'old' is not a valid option. select from 'hot', 'cold', 'rain', 'snow'

?

4
  • I revised your presentation of the question because, as far as I can tell, it doesn't have anything to do with exceptions (the language facility). If I have misunderstood what you meant to ask, please let me know. Commented Jun 20, 2015 at 17:08
  • Which python version are you using and which os? I tried in Python 3.4 in windows, and I am not getting the issue you said Commented Jun 20, 2015 at 17:15
  • @zwol question reads much better. thanks! Commented Jun 20, 2015 at 19:56
  • So I'm ten years late here, but actually exception handling is relevant here because argparse handles some types of exceptions in a special way. However, that feature does seem to be underdocumented, and it's definitely easy to miss. Commented Sep 9 at 13:03

3 Answers 3

7

Looking at the source code, you could over-ride this particular error message by overriding this method:

def _check_value(self, action, value):
    # converted value must be one of the choices (if specified)
    if action.choices is not None and value not in action.choices:
        args = {'value': value,
                'choices': ', '.join(map(repr, action.choices))}
        msg = _('invalid choice: %(value)r (choose from %(choices)s)')
        raise ArgumentError(action, msg % args)

The issue is that if you wanted to override all possible error messages, you'd have to basically re-write this module. All the various error messages are pre-formatted throughout -in the various methods that detect each type of error.

Sign up to request clarification or add additional context in comments.

2 Comments

After posting the question, I began to realize that there probably wasn't a general solution. Like you say, you'd have to re-write the module to deliver custom error .messages which defeats the purpose.
@HenryThornton There should be a PEP for custom error potential. There isn't right now.
1

The way to do this is to use an ArgumentTypeError. argparse has special handling for ArgumentTypeError, TypeError, and ValueError, and for the latter two it ignores the original error message, but for the first one it preserves the exception message. The documentation doesn't make this difference clear, but there was [a bug report](https://bugs.python.org/issue30220) where it was discussed before, and it was concluded that using this exception was the recommended way to customize error messages.

import argparse

def is_weather(word):
    if word not in ("hot", "cold", "rain", "snow"):
        raise argparse.ArgumentTypeError("OH NO! Not weather: " + word)

    return word

parser = argparse.ArgumentParser()
parser.add_argument("weather", type=is_weather)

args = parser.parse_args()

print("The weather is " + args.weather)

If you run python weather.py blarg, the output is:

usage: check.py [-h] weather
check.py: error: argument weather: OH NO! Not weather: blarg

Using this kind of exception, you can pass custom error messages without messing with the internals of argparse.

Comments

1

Adding to @Gerrat's answer, the _ function is imported as

from gettext import gettext as _, ngettext

It is using the gettext module, https://docs.python.org/2/library/gettext.html, to enable internationalization. I'm not familiar with that module, but presumably you could use it to perform a certain amount English paraphrasing. But maybe not as much as you'd like.

Error messages pass through several levels. Functions like _check_values write the basic message. ArgumentError adds the argument name (argument subparser: in your example). parser.error adds the usage and prog. parser.exit takes care of the sys.exit step.

def error(self, message):
    ...
    self.print_usage(_sys.stderr)
    args = {'prog': self.prog, 'message': message}
    self.exit(2, _('%(prog)s: error: %(message)s\n') % args)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.