Skip to content

DM-42226: Recommend pyproject.toml be used for command-lines not bin.src #708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading