From 5327815157dc0bda3eec59c3141d19dc58f786da Mon Sep 17 00:00:00 2001 From: Tim Jenness Date: Thu, 16 Jan 2025 13:51:35 -0700 Subject: [PATCH] Recommend pyproject.toml be used for command-lines not bin.src --- python/cli.rst | 24 ++++++++++++++++++------ stack/argparse-script-topic-type.rst | 14 ++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/python/cli.rst b/python/cli.rst index ef9e5b313..a07924d76 100644 --- a/python/cli.rst +++ b/python/cli.rst @@ -33,11 +33,24 @@ Read more in the `argparse documentation`_. .. _argparse documentation: https://docs.python.org/3/library/argparse.html +.. _add_callable_cli_command_to_your_package: + Add a Callable CLI Command To Your Package ========================================== -To create a callable command at the top level of your package create a folder called ``bin.src``. -It should contain two files: +If your callable command is implemented as described in :ref:`argparse-script-topic-type` with a single function loading from the package implementing the script, you can define the script in the `standard Python package approach using `_ ``pyproject.toml``. +For example, this: + +.. code-block:: toml + + [project.scripts] + exampleScript = "lsst.example.scripts.exampleScript:main" + +will result in a executable file appearing in ``bin/exampleScript`` that will import ``main`` from ``lsst.example.scripts.exampleScript`` and call it. +This is the recommended way to define callable commands and all newly-written scripts should be defined this way. + +If your script is monolithic and includes the implementation directly in the callable script and you cannot reorganize the code, you must instead write the command to a ``bin.src`` directory at the top level. +The directory should contain: 1. ``SConscript`` with contents: @@ -47,8 +60,7 @@ It should contain two files: from lsst.sconsUtils import scripts scripts.BasicSConscript.shebang() -2. A file that has the name of the CLI command the user will call. - This file should contain as little implementation as possible. - The ideal simplest case is to import an implementation function and call it. - This makes the implementation testable and reusable. +2. A file for each command that has the name of the CLI command the user will call. + This file should have a Python shebang (``#!``) in the first line. +It is possible for a package to define some scripts in ``pyproject.toml`` and some scripts in ``bin.src`` but it is an error if a script is defined in both places. diff --git a/stack/argparse-script-topic-type.rst b/stack/argparse-script-topic-type.rst index 35c0ba760..7c87406bb 100644 --- a/stack/argparse-script-topic-type.rst +++ b/stack/argparse-script-topic-type.rst @@ -60,19 +60,9 @@ autoprogram_ generates all the content that is described by the :doc:`script top To use the autoprogram_ directive, your script needs to be set up in a particular fashion: -- The script needs to be in the ``bin.src`` directory of a package, but the script file must defer its implementation to a module *inside* the package's Python modules. +- The script needs to be defined such that the implementation is in a module *inside* the package's Python modules, with the script function specified in the package's ``pyproject.toml`` (see :ref:`add_callable_cli_command_to_your_package` for more information). - For example, a script file :file:`bin.src/exampleScript.py` might be structured like this: - - .. code-block:: python - - #! /usr/bin/env python - - from lsst.example.scripts.exampleScript import main - - - if __name__ == "__main__": - main() + For example, a script file might be implemented as a ``main`` function found in ``lsst.example.scripts.exampleScript`` and be defined as a command line program called ``exampleScript``. - The `argparse.ArgumentParser` instance must be generated by an argument-less function. This is critical for letting the autoprogram_ directive get a copy of the `~argparse.ArgumentParser` instance to introspect the command-line interface: