2

I´m currently trying to customize the error handling when a command was given without providing needed arguments using Click. According to this SO question this can be done by overriding the show function of click.exceptions.UsageError. However, I tried to modify the provided solution there but I couldn´t get it to work.

In my case, I want to be able to get the command that should be executed (but failed due to missing arguments) and depending on the entered command, I want to process further. My sample code looks like this:

@click.group(cls=MyGroup)
def myapp():
    pass

@myapp.command()
@click.argument('myarg',type=str)
def mycommand(myarg: str) -> None:
    do_stuff(myarg)

So if the command was something like myapp mycommand and it misses needed arguments I want to handle it individually. I searched for a while, but I was not able to figure out HOW to fetch the command (I tried passing the context but as far as I read, UsageError gets no context passed on initialization).

I´d be grateful for any hint or ideas.

EDIT: The implemenation of myGroup looks like this:

class myGroup(click.Group):
"""
Customize help order and get_command
https://stackoverflow.com/a/47984810/713980
"""

def __init__(self, *args, **kwargs):
    self.help_priorities = {}
    super(myGroup, self).__init__(*args, **kwargs)

def get_help(self, ctx):
    self.list_commands = self.list_commands_for_help
    return super(myGroup, self).get_help(ctx)

def list_commands_for_help(self, ctx):
    """reorder the list of commands when listing the help"""
    commands = super(myGroup, self).list_commands(ctx)
    return (c[1] for c in sorted((self.help_priorities.get(command, 1000), command) for command in commands))

def command(self, *args, **kwargs):
    """Behaves the same as `click.Group.command()` except capture
    a priority for listing command names in help.
    """
    help_priority = kwargs.pop('help_priority', 1000)
    help_priorities = self.help_priorities

    def decorator(f):
        cmd = super(myGroup, self).command(*args, **kwargs)(f)
        help_priorities[cmd.name] = help_priority
        return cmd
    return decorator

def get_command(self, ctx, cmd_name):
    rv = click.Group.get_command(self, ctx, cmd_name)
    if rv is not None:
        return rv
    sim_commands = most_sim_com(cmd_name, COMMANDS)

    matches = [cmd for cmd in self.list_commands(ctx) if cmd in sim_commands]
    if not matches:
        ctx.fail(click.style('Unknown command and no similar command was found!', fg='red'))
    elif len(matches) == 1:
        click.echo(click.style(f'Unknown command! Will use best match {matches[0]}.', fg='red'))
        return click.Group.get_command(self, ctx, matches[0])
    ctx.fail(click.style(f'Unknown command. Most similar commands were {", ".join(sorted(matches))}', fg='red'))
4
  • 1
    Hello again. Could you post your implementation of MyGroup please? Commented Apr 28, 2020 at 11:47
  • Hey ;), I edited my post above. Commented Apr 28, 2020 at 16:56
  • so, to clarify, you want to be able to have a custom error message if myarg isn't passed into your command invocation? Commented Apr 28, 2020 at 20:12
  • 1
    Yes, but additionally I want to be able to actually know which command has been invoked in order to do a bunch of other stuff depending on that command Commented Apr 28, 2020 at 20:14

2 Answers 2

2

This is a first draft and the most naive solution I can think of, so it might change it if doesn't fully solve your problem. Would changing your code to something like this help?

@click.group(cls=MyGroup)
def myapp():
    pass

@myapp.command()
@click.argument('myarg',type=str, required=False)
def mycommand(myarg: str=None) -> None:
    validate_my_command(myarg) # this is where you do your custom logic and error message handling

The advantage of doing it this way is that it's explicit and holding in line with Click's suggested way of doing this. However, if you wanted to do this for every command, we can consider more complex approaches

Let me know what you think

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

3 Comments

Cool, nice solution didn't think of setting required to False that's very intuitive. I think I will go with this for my other commands as well and avoid implementing anything too complex. Out of curiosity: What would be a more complex approach like?
I couldn't really tell you. I imagine something along the lines of either decorating a click command that does this validation for you, which sounds doable. Or, rolling your own implementation of a Command, but that's liable to change whenever the Click API changes (not that often, but something to be mindful of).
Creating my own decorator sounds quite interesting. But nevertheless thanks again ;)
0

These days click.command has the no_args_is_help parameter, see docs. This was introduced in version 7.1.

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.