Skip to content

Commit

Permalink
Recommend pyproject.toml be used for command-lines not bin.src
Browse files Browse the repository at this point in the history
  • Loading branch information
timj committed Jan 16, 2025
1 parent a70e672 commit 5327815
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 18 deletions.
24 changes: 18 additions & 6 deletions python/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#creating-executable-scripts>`_ ``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:

Expand All @@ -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.
14 changes: 2 additions & 12 deletions stack/argparse-script-topic-type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 5327815

Please sign in to comment.