Creating and packaging command-line tools#
This guide will walk you through creating and packaging a standalone command-line application that can be installed with pipx, a tool creating and managing Python Virtual Environments and exposing the executable scripts of packages (and available manual pages) for use on the command-line.
Creating the package#
First of all, create a source tree for the project. For the sake of an example, we'll build a simple tool outputting a greeting (a string) for a person based on arguments given on the command-line.
課題
Advise on the optimal structure of a Python package in another guide or discussion and link to it here.
This project will adhere to src-layout and in the end be alike this file tree,
with the top-level folder and package name greetings
:
.
├── pyproject.toml
└── src
└── greetings
├── cli.py
├── greet.py
├── __init__.py
└── __main__.py
The actual code responsible for the tool's functionality will be stored in the file greet.py
,
named after the main module:
import typer
from typing_extensions import Annotated
def greet(
name: Annotated[str, typer.Argument(help="The (last, if --gender is given) name of the person to greet")] = "",
gender: Annotated[str, typer.Option(help="The gender of the person to greet")] = "",
knight: Annotated[bool, typer.Option(help="Whether the person is a knight")] = False,
count: Annotated[int, typer.Option(help="Number of times to greet the person")] = 1
):
greeting = "Greetings, dear "
masculine = gender == "masculine"
feminine = gender == "feminine"
if gender or knight:
salutation = ""
if knight:
salutation = "Sir "
elif masculine:
salutation = "Mr. "
elif feminine:
salutation = "Ms. "
greeting += salutation
if name:
greeting += f"{name}!"
else:
pronoun = "her" if feminine else "his" if masculine or knight else "its"
greeting += f"what's-{pronoun}-name"
else:
if name:
greeting += f"{name}!"
elif not gender:
greeting += "friend!"
for i in range(0, count):
print(greeting)
The above function receives several keyword arguments that determine how the greeting to output is constructed.
Now, construct the command-line interface to provision it with the same, which is done
in cli.py
:
import typer
from .greet import greet
app = typer.Typer()
app.command()(greet)
if __name__ == "__main__":
app()
The command-line interface is built with typer, an easy-to-use CLI parser based on Python type hints. It provides
auto-completion and nicely styled command-line help out of the box. Another option would be argparse
,
a command-line parser which is included in Python's standard library. It is sufficient for most needs, but requires
a lot of code, usually in cli.py
, to function properly. Alternatively, docopt makes it possible to create CLI
interfaces based solely on docstrings; advanced users are encouraged to make use of click (on which typer
is based).
Now, add an empty __init__.py
file, to define the project as a regular import package.
The file __main__.py
marks the main entry point for the application when running it via runpy
(i.e. python -m greetings
, which works immediately with flat layout, but requires installation of the package with src layout),
so initizalize the command-line interface here:
if __name__ == "__main__":
from greetings.cli import app
app()
注釈
In order to enable calling the command-line interface directly from the source tree,
i.e. as python src/greetings
, a certain hack could be placed in this file; read more at
Running a command-line interface from source with src-layout.
pyproject.toml
#
The project's metadata is placed in pyproject.toml. The pyproject metadata keys and the [build-system]
table may be filled in as described in pyproject.toml を書く, adding a dependency
on typer
(this tutorial uses version 0.12.3).
For the project to be recognised as a command-line tool, additionally a console_scripts
entry point (see 実行可能なスクリプトを作成する) needs to be added as a subkey:
[project.scripts]
greet = "greetings.cli:app"
Now, the project's source tree is ready to be transformed into a distribution package, which makes it installable.
Installing the package with pipx
#
After installing pipx
as described in スタンドアローンのコマンドラインツールをインストールする, install your project:
$ cd path/to/greetings/
$ pipx install .
This will expose the executable script we defined as an entry point and make the command greet
available.
Let's test it:
$ greet --knight Lancelot
Greetings, dear Sir Lancelot!
$ greet --gender feminine Parks
Greetings, dear Ms. Parks!
$ greet --gender masculine
Greetings, dear Mr. what's-his-name!
Since this example uses typer
, you could now also get an overview of the program's usage by calling it with
the --help
option, or configure completions via the --install-completion
option.
To just run the program without installing it permanently, use pipx run
, which will create a temporary
(but cached) virtual environment for it:
$ pipx run --spec . greet --knight
This syntax is a bit unpractical, however; as the name of the entry point we defined above does not match the package name, we need to state explicitly which executable script to run (even though there is only on in existence).
There is, however, a more practical solution to this problem, in the form of an entry point specific to pipx run
.
The same can be defined as follows in pyproject.toml
:
[project.entry-points."pipx.run"]
greetings = "greetings.cli:app"
Thanks to this entry point (which must match the package name), pipx
will pick up the executable script as the
default one and run it, which makes this command possible:
$ pipx run . --knight
Conclusion#
You know by now how to package a command-line application written in Python. A further step could be to distribute you package, meaning uploading it to a package index, most commonly PyPI. To do that, follow the instructions at プロジェクトをパッケージングする. And once you're done, don't forget to do some research on how your package is received!