|
| 1 | +(exception-handling-exit-codes)= |
| 2 | + |
| 3 | +# Exception Handling and Exit Codes |
| 4 | + |
| 5 | +```{eval-rst} |
| 6 | +.. currentmodule:: click |
| 7 | +``` |
| 8 | + |
| 9 | +Click internally uses exceptions to signal various error conditions that |
| 10 | +the user of the application might have caused. Primarily this is things |
| 11 | +like incorrect usage. |
| 12 | + |
| 13 | +```{contents} |
| 14 | +:depth: 1 |
| 15 | +:local: |
| 16 | +``` |
| 17 | + |
| 18 | +## Where are Errors Handled? |
| 19 | + |
| 20 | +Click's main error handling is happening in {meth}`Command.main`. In |
| 21 | +there it handles all subclasses of {exc}`ClickException` as well as the |
| 22 | +standard {exc}`EOFError` and {exc}`KeyboardInterrupt` exceptions. The |
| 23 | +latter are internally translated into an {exc}`Abort`. |
| 24 | + |
| 25 | +The logic applied is the following: |
| 26 | + |
| 27 | +1. If an {exc}`EOFError` or {exc}`KeyboardInterrupt` happens, reraise it |
| 28 | + as {exc}`Abort`. |
| 29 | +2. If a {exc}`ClickException` is raised, invoke the |
| 30 | + {meth}`ClickException.show` method on it to display it and then exit |
| 31 | + the program with {attr}`ClickException.exit_code`. |
| 32 | +3. If an {exc}`Abort` exception is raised print the string ``Aborted!`` |
| 33 | + to standard error and exit the program with exit code ``1``. |
| 34 | +4. If it goes through well, exit the program with exit code ``0``. |
| 35 | + |
| 36 | +## What if I Don't Want That? |
| 37 | + |
| 38 | +Generally you always have the option to invoke the {meth}`Command.invoke` |
| 39 | +method yourself. For instance if you have a {class}`Command` you can |
| 40 | +invoke it manually like this: |
| 41 | + |
| 42 | +```python |
| 43 | +ctx = command.make_context("command-name", ["args", "go", "here"]) |
| 44 | +with ctx: |
| 45 | + result = command.invoke(ctx) |
| 46 | +``` |
| 47 | + |
| 48 | +In this case exceptions will not be handled at all and bubbled up as you |
| 49 | +would expect. |
| 50 | + |
| 51 | +Starting with Click 3.0 you can also use the {meth}`Command.main` method |
| 52 | +but disable the standalone mode which will do two things: disable |
| 53 | +exception handling and disable the implicit {func}`sys.exit` at the end. |
| 54 | + |
| 55 | +So you can do something like this: |
| 56 | + |
| 57 | +```python |
| 58 | +command.main( |
| 59 | + ["command-name", "args", "go", "here"], |
| 60 | + standalone_mode=False, |
| 61 | +) |
| 62 | +``` |
| 63 | + |
| 64 | +## Which Exceptions Exist? |
| 65 | + |
| 66 | +Click has two exception bases: {exc}`ClickException` which is raised for |
| 67 | +all exceptions that Click wants to signal to the user and {exc}`Abort` |
| 68 | +which is used to instruct Click to abort the execution. |
| 69 | + |
| 70 | +A {exc}`ClickException` has a {meth}`ClickException.show` method which |
| 71 | +can render an error message to stderr or the given file object. If you |
| 72 | +want to use the exception yourself for doing something check the API docs |
| 73 | +about what else they provide. |
| 74 | + |
| 75 | +The following common subclasses exist: |
| 76 | + |
| 77 | +- {exc}`UsageError` to inform the user that something went wrong. |
| 78 | +- {exc}`BadParameter` to inform the user that something went wrong with |
| 79 | + a specific parameter. These are often handled internally in Click and |
| 80 | + augmented with extra information if possible. For instance if those |
| 81 | + are raised from a callback Click will automatically augment it with |
| 82 | + the parameter name if possible. |
| 83 | +- {exc}`FileError` this is an error that is raised by the |
| 84 | + {class}`FileType` if Click encounters issues opening the file. |
| 85 | + |
| 86 | +(help-page-exit-codes)= |
| 87 | + |
| 88 | +## Help Pages and Exit Codes |
| 89 | + |
| 90 | +Triggering the a help page intentionally (by passing in ``--help``) |
| 91 | +returns exit code 0. If a help page is displayed due to incorrect user |
| 92 | +input, the program returns exit code 2. See {ref}`exit-codes` for more |
| 93 | +general information. |
| 94 | + |
| 95 | +For clarity, here is an example. |
| 96 | + |
| 97 | +```{eval-rst} |
| 98 | +.. click:example:: |
| 99 | +
|
| 100 | + @click.group('printer_group') |
| 101 | + def printer_group(): |
| 102 | + pass |
| 103 | +
|
| 104 | + @printer_group.command('printer') |
| 105 | + @click.option('--this') |
| 106 | + def printer(this): |
| 107 | + if this: |
| 108 | + click.echo(this) |
| 109 | +
|
| 110 | +.. click:run:: |
| 111 | + invoke(printer_group, args=['--help']) |
| 112 | +
|
| 113 | +The above invocation returns exit code 0. |
| 114 | +
|
| 115 | +.. click:run:: |
| 116 | + invoke(printer_group, args=[]) |
| 117 | +``` |
| 118 | + |
| 119 | +The above invocation returns exit code 2 since the user invoked the command incorrectly. However, since this is such a common error when first using a command, Click invokes the help page for the user. To see that `printer-group` is an invalid invocation, turn `no_args_is_help` off. |
| 120 | + |
| 121 | +```{eval-rst} |
| 122 | +.. click:example:: |
| 123 | +
|
| 124 | + @click.group('printer_group', no_args_is_help=False) |
| 125 | + def printer_group(): |
| 126 | + pass |
| 127 | +
|
| 128 | + @printer_group.command('printer') |
| 129 | + @click.option('--this') |
| 130 | + def printer(this): |
| 131 | + if this: |
| 132 | + click.echo(this) |
| 133 | +
|
| 134 | +.. click:run:: |
| 135 | + invoke(printer_group, args=[]) |
| 136 | +``` |
0 commit comments