diff --git a/docs/entry-points.rst b/docs/entry-points.md similarity index 68% rename from docs/entry-points.rst rename to docs/entry-points.md index 6c70f9724..200f5b5e2 100644 --- a/docs/entry-points.rst +++ b/docs/entry-points.md @@ -1,5 +1,8 @@ -Packaging Entry Points -====================== +# Packaging Entry Points + +```{eval-rst} +.. currentmodule:: click +``` It's recommended to write command line utilities as installable packages with entry points instead of telling users to run ``python hello.py``. @@ -14,74 +17,72 @@ function. These are known as "entry points". The installer knows how to create an executable regardless of the operating system, so it will work on Linux, Windows, MacOS, etc. - -Project Files -------------- +## Project Files To install your app with an entry point, all you need is the script and a ``pyproject.toml`` file. Here's an example project directory: -.. code-block:: text - - hello-project/ - src/ - hello/ - __init__.py - hello.py - pyproject.toml +```text +hello-project/ + src/ + hello/ + __init__.py + hello.py + pyproject.toml +``` Contents of ``hello.py``: +```{eval-rst} .. click:example:: - import click @click.command() def cli(): """Prints a greeting.""" click.echo("Hello, World!") +``` Contents of ``pyproject.toml``: -.. code-block:: toml +```toml +[project] +name = "hello" +version = "1.0.0" +description = "Hello CLI" +requires-python = ">=3.11" +dependencies = [ + "click>=8.1", +] - [project] - name = "hello" - version = "1.0.0" - description = "Hello CLI" - requires-python = ">=3.11" - dependencies = [ - "click>=8.1", - ] +[project.scripts] +hello = "hello.hello:cli" - [project.scripts] - hello = "hello.hello:cli" - - [build-system] - requires = ["flit_core<4"] - build-backend = "flit_core.buildapi" +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" +``` The magic is in the ``project.scripts`` section. Each line identifies one executable script. The first part before the equals sign (``=``) is the name of the script that should be generated, the second part is the import path followed by a colon (``:``) with the function to call (the Click command). - -Installation ------------- +## Installation When your package is installed, the installer will create an executable script based on the configuration. During development, you can install in editable mode using the ``-e`` option. Remember to use a virtual environment! -.. code-block:: console - - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install -e . +```console +$ python -m venv .venv +$ . .venv/bin/activate +$ pip install -e . +``` Afterwards, your command should be available: -.. click:run:: - - invoke(cli, prog_name="hello") +```console +$ hello +Hello, World! +``` diff --git a/docs/exceptions.md b/docs/exceptions.md new file mode 100644 index 000000000..a20228d6f --- /dev/null +++ b/docs/exceptions.md @@ -0,0 +1,136 @@ +(exception-handling-exit-codes)= + +# Exception Handling and Exit Codes + +```{eval-rst} +.. currentmodule:: click +``` + +Click internally uses exceptions to signal various error conditions that +the user of the application might have caused. Primarily this is things +like incorrect usage. + +```{contents} +:depth: 1 +:local: +``` + +## Where are Errors Handled? + +Click's main error handling is happening in {meth}`Command.main`. In +there it handles all subclasses of {exc}`ClickException` as well as the +standard {exc}`EOFError` and {exc}`KeyboardInterrupt` exceptions. The +latter are internally translated into an {exc}`Abort`. + +The logic applied is the following: + +1. If an {exc}`EOFError` or {exc}`KeyboardInterrupt` happens, reraise it + as {exc}`Abort`. +2. If a {exc}`ClickException` is raised, invoke the + {meth}`ClickException.show` method on it to display it and then exit + the program with {attr}`ClickException.exit_code`. +3. If an {exc}`Abort` exception is raised print the string ``Aborted!`` + to standard error and exit the program with exit code ``1``. +4. If it goes through well, exit the program with exit code ``0``. + +## What if I Don't Want That? + +Generally you always have the option to invoke the {meth}`Command.invoke` +method yourself. For instance if you have a {class}`Command` you can +invoke it manually like this: + +```python +ctx = command.make_context("command-name", ["args", "go", "here"]) +with ctx: + result = command.invoke(ctx) +``` + +In this case exceptions will not be handled at all and bubbled up as you +would expect. + +Starting with Click 3.0 you can also use the {meth}`Command.main` method +but disable the standalone mode which will do two things: disable +exception handling and disable the implicit {func}`sys.exit` at the end. + +So you can do something like this: + +```python +command.main( + ["command-name", "args", "go", "here"], + standalone_mode=False, +) +``` + +## Which Exceptions Exist? + +Click has two exception bases: {exc}`ClickException` which is raised for +all exceptions that Click wants to signal to the user and {exc}`Abort` +which is used to instruct Click to abort the execution. + +A {exc}`ClickException` has a {meth}`ClickException.show` method which +can render an error message to stderr or the given file object. If you +want to use the exception yourself for doing something check the API docs +about what else they provide. + +The following common subclasses exist: + +- {exc}`UsageError` to inform the user that something went wrong. +- {exc}`BadParameter` to inform the user that something went wrong with + a specific parameter. These are often handled internally in Click and + augmented with extra information if possible. For instance if those + are raised from a callback Click will automatically augment it with + the parameter name if possible. +- {exc}`FileError` this is an error that is raised by the + {class}`FileType` if Click encounters issues opening the file. + +(help-page-exit-codes)= + +## Help Pages and Exit Codes + +Triggering the a help page intentionally (by passing in ``--help``) +returns exit code 0. If a help page is displayed due to incorrect user +input, the program returns exit code 2. See {ref}`exit-codes` for more +general information. + +For clarity, here is an example. + +```{eval-rst} +.. click:example:: + + @click.group('printer_group') + def printer_group(): + pass + + @printer_group.command('printer') + @click.option('--this') + def printer(this): + if this: + click.echo(this) + +.. click:run:: + invoke(printer_group, args=['--help']) + +The above invocation returns exit code 0. + +.. click:run:: + invoke(printer_group, args=[]) +``` + +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. + +```{eval-rst} +.. click:example:: + + @click.group('printer_group', no_args_is_help=False) + def printer_group(): + pass + + @printer_group.command('printer') + @click.option('--this') + def printer(this): + if this: + click.echo(this) + +.. click:run:: + invoke(printer_group, args=[]) +``` diff --git a/docs/exceptions.rst b/docs/exceptions.rst deleted file mode 100644 index 3e73a078a..000000000 --- a/docs/exceptions.rst +++ /dev/null @@ -1,126 +0,0 @@ -.. _exception-handling-exit-codes: - -Exception Handling and Exit Codes -================================== - -.. currentmodule:: click - -Click internally uses exceptions to signal various error conditions that -the user of the application might have caused. Primarily this is things -like incorrect usage. - -.. contents:: - :depth: 1 - :local: - -Where are Errors Handled? -------------------------- - -Click's main error handling is happening in :meth:`Command.main`. In -there it handles all subclasses of :exc:`ClickException` as well as the -standard :exc:`EOFError` and :exc:`KeyboardInterrupt` exceptions. The -latter are internally translated into an :exc:`Abort`. - -The logic applied is the following: - -1. If an :exc:`EOFError` or :exc:`KeyboardInterrupt` happens, reraise it - as :exc:`Abort`. -2. If a :exc:`ClickException` is raised, invoke the - :meth:`ClickException.show` method on it to display it and then exit - the program with :attr:`ClickException.exit_code`. -3. If an :exc:`Abort` exception is raised print the string ``Aborted!`` - to standard error and exit the program with exit code ``1``. -4. If it goes through well, exit the program with exit code ``0``. - -What if I don't want that? --------------------------- - -Generally you always have the option to invoke the :meth:`invoke` method -yourself. For instance if you have a :class:`Command` you can invoke it -manually like this:: - - ctx = command.make_context('command-name', ['args', 'go', 'here']) - with ctx: - result = command.invoke(ctx) - -In this case exceptions will not be handled at all and bubbled up as you -would expect. - -Starting with Click 3.0 you can also use the :meth:`Command.main` method -but disable the standalone mode which will do two things: disable -exception handling and disable the implicit :meth:`sys.exit` at the end. - -So you can do something like this:: - - command.main(['command-name', 'args', 'go', 'here'], - standalone_mode=False) - -Which Exceptions Exist? ------------------------ - -Click has two exception bases: :exc:`ClickException` which is raised for -all exceptions that Click wants to signal to the user and :exc:`Abort` -which is used to instruct Click to abort the execution. - -A :exc:`ClickException` has a :meth:`~ClickException.show` method which -can render an error message to stderr or the given file object. If you -want to use the exception yourself for doing something check the API docs -about what else they provide. - -The following common subclasses exist: - -* :exc:`UsageError` to inform the user that something went wrong. -* :exc:`BadParameter` to inform the user that something went wrong with - a specific parameter. These are often handled internally in Click and - augmented with extra information if possible. For instance if those - are raised from a callback Click will automatically augment it with - the parameter name if possible. -* :exc:`FileError` this is an error that is raised by the - :exc:`FileType` if Click encounters issues opening the file. - - -.. _help-page-exit-codes: - -Help Pages and Exit Codes --------------------------- - -Triggering the a help page intentionally (by passing in ``--help``) returns exit code 0. If a help page is displayed due to incorrect user input, the program returns exit code 2. See :ref:`exit-codes` for more general information. - -For clarity, here is an example. - -.. click:example:: - - @click.group('printer_group') - def printer_group(): - pass - - @printer_group.command('printer') - @click.option('--this') - def printer(this): - if this: - click.echo(this) - -.. click:run:: - invoke(printer_group, args=['--help']) - -The above invocation returns exit code 0. - -.. click:run:: - invoke(printer_group, args=[]) - -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. - -.. click:example:: - - @click.group('printer_group', no_args_is_help=False) - def printer_group(): - pass - - @printer_group.command('printer') - @click.option('--this') - def printer(this): - if this: - click.echo(this) - -.. click:run:: - invoke(printer_group, args=[])