Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Click: custom error message

I use the excellent Python Click library for handling command line options in my tool. Here's a simplified version of my code (full script here):

@click.command(
    context_settings = dict( help_option_names = ['-h', '--help'] )
)
@click.argument('analysis_dir',
                 type = click.Path(exists=True),
                 nargs = -1,
                 required = True,
                 metavar = "<analysis directory>"
)

def mytool(analysis_dir):
   """ Do stuff """

if __name__ == "__main__":
    mytool()

If someone runs the command without any flags, they get the default click error message:

$ mytool

Usage: mytool [OPTIONS] <analysis directory>

Error: Missing argument "analysis_dir".

This is nice, but I'd quite like to tell (very) novice users that more help is available by using the help flag. In other words, add a custom sentence to the error message when the command is invalid telling people to try mytool --help for more information.

Is there an easy way to do this? I know I could remove the required attribute and handle this logic in the main function, but that feels kind of hacky for such a minor addition.

like image 318
ewels Avatar asked Sep 20 '16 13:09

ewels


1 Answers

Message construction for most errors in python-click is handled by the show method of the UsageError class: click.exceptions.UsageError.show.

So, if you redefine this method, you will be able to create your own customized error message. Below is an example of a customization which appends the help menu to any error message which answers this SO question:

def modify_usage_error(main_command):
    '''
        a method to append the help menu to an usage error

    :param main_command: top-level group or command object constructed by click wrapper 
    :return: None
    '''

    from click._compat import get_text_stderr
    from click.utils import echo
    def show(self, file=None):
        import sys
        if file is None:
            file = get_text_stderr()
        color = None
        if self.ctx is not None:
            color = self.ctx.color
            echo(self.ctx.get_usage() + '\n', file=file, color=color)
        echo('Error: %s\n' % self.format_message(), file=file, color=color)
        sys.argv = [sys.argv[0]]
        main_command()

    click.exceptions.UsageError.show = show

Once you define your main command, you can then run the modifier script:

import click
@click.group()
def cli():
    pass

modify_usage_error(cli)

I have not explored whether there are runtime invocations of ClickException other than usage errors. If there are, then you might need to modify your custom error handler to first check that ctx is an attribute before you add the line click.exceptions.ClickException.show = show since it does not appear that ClickException is fed ctx at initialization.

like image 134
R J Avatar answered Sep 21 '22 19:09

R J