Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to not exit a click CLI?

I'm writing a python script, that should behave like a typical shell and providing some self written functions. It is working quite well already, but it always exits after a successful command, so that it has to be started again to perform a second task. How can I make it, so it doesn't finish with exit code 0 but returns to shell awaiting new input? How would I have to implement exit methods then? Following example always exits after typing print-a or print-b:

import click
import click_repl
from prompt_toolkit.history import FileHistory
import os

@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
     if ctx.invoked_subcommand is None:
         ctx.invoke(repl)

@cli.command()
def print_a():
    print("a")


@cli.command()
def print_b():
    print("b")


@cli.command()
def repl():
    prompt_kwargs = {
        'history': FileHistory(os.path.expanduser('~/.repl_history'))
    }
    click_repl.repl(click.get_current_context(), prompt_kwargs)

def main():
    while True:
        cli(obj={})


if __name__ == "__main__":
    main()

(And a bonus question: In the cmd package it is possible to customize the > prompt tag, is this possible with click to? So that it's something like App> instead?)

like image 612
AracKnight Avatar asked Jan 02 '23 18:01

AracKnight


2 Answers

Use the standalone_mode argument, try this:

rv = cli(obj={}, standalone_mode=False)

When parsing failed, the code above will throw a UsageError. When --help was passed, rv will be the integer 0. In most other cases the return value of the function that handles the command is returned, although there are a bunch of exceptions and the behavior in general is quite complex, more explanations here: https://click.palletsprojects.com/en/master/commands/#command-return-values

The advantage of this approach is that you can use return values from command handlers. The disadvantage is that you lose the pretty printed help message when parsing failed (maybe there is way to restore it?).


Another option is to not use standalone_mode and instead wrap your call to cli in a try/except block where you catch a SystemExit:

try:
    cli(obj={})
except SystemExit as e:
    if e.code != 0:
        raise

By catching SystemExit you can stop the program exit process initiated by click. If the command parsed succesfully then SystemExit(0) is caught. Note again that parsing --help also counts as a 'successfull' parse, and therefore also returns SystemExit(0).

The disadvantage of this approach is that you cannot use the return value of a command handler, which makes it more difficult to know when --help was passed. The upside is that all help messages to the console are restored.

I should also note that SystemExit inherits from BaseException but not from Exception. So to actually catch SystemExit you can either catch it directly or catch BaseException.

like image 196
Sam De Meyer Avatar answered Jan 11 '23 18:01

Sam De Meyer


You could check out click-shell, which is a wrapper for click and the python cmd module. It supports auto completion and help from docstrings out of the box.

like image 26
Dan Arad Avatar answered Jan 11 '23 20:01

Dan Arad