Making packages compatible¶
Developing for pipx¶
If you are a developer and want users to be able to run
$ pipx install MY_PACKAGE
make sure you include scripts in your main table [1] in pyproject.toml or its legacy equivalents for
setup.cfg and setup.py. pipx also exposes gui-scripts entry points, which are useful for GUI applications on
Windows (they launch without opening a console window).
[project.scripts]
foo = "my_package.some_module:main_func"
bar = "other_module:some_func"
[project.gui-scripts]
baz = "my_package_gui:start_func"
[options.entry_points]
console_scripts =
foo = my_package.some_module:main_func
bar = other_module:some_func
gui_scripts =
baz = my_package_gui:start_func
setup(
# other arguments here...
entry_points={
"console_scripts": [
"foo = my_package.some_module:main_func",
"bar = other_module:some_func",
],
"gui_scripts": [
"baz = my_package_gui:start_func",
],
},
)
Here foo and bar (and baz on Windows) become applications that pipx exposes after installing the package,
invoking their corresponding entry point functions.
flowchart LR
TOML["pyproject.toml<br/>[project.scripts]"] -->|defines| EP["entry points<br/>foo, bar"]
EP -->|pipx install| VENV["isolated venv"]
VENV -->|symlink or copy| BIN["~/.local/bin/<br/>foo, bar"]
BIN -->|invokes| FUNC["my_package:<br/>main_func()"]
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
classDef venv fill:#7c4dff,stroke:#5a2fd0,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
classDef fn fill:#c78c20,stroke:#8a6011,color:#fff
class TOML input
class EP proc
class VENV venv
class BIN out
class FUNC fn
The pipx.run entry point group¶
When a user runs pipx run PACKAGE, pipx looks for a console script matching the package name. If the package name
and script name differ, the user has to write pipx run --spec PACKAGE SCRIPT, which is less convenient.
Package authors can declare a pipx.run entry point group to tell pipx which function to invoke for pipx run.
This entry point takes priority over console scripts when present.
[project.entry-points."pipx.run"]
my-package = "my_package.cli:main"
[options.entry_points]
pipx.run =
my-package = my_package.cli:main
With this declaration, pipx run my-package invokes my_package.cli:main even if no console script named
my-package exists. The build package uses this pattern so that pipx run build
works even though build’s console script is named pyproject-build.
Detect a pipx installation¶
pipx records the environment name in pipx_metadata.json for packages installed with pipx install. Package code
can read this name when it needs to give pipx-specific recovery instructions:
import json
import sys
from pathlib import Path
def pipx_environment() -> str | None:
try:
metadata = json.loads((Path(sys.prefix) / "pipx_metadata.json").read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None
environment = metadata.get("environment") if isinstance(metadata, dict) else None
return environment if isinstance(environment, str) else None
Use the returned name as the target for commands such as pipx inject ENVIRONMENT 'mytool[feature]'. pipx records the
extra on the main package, so reinstalling the environment retains it.
pipx run may create internal metadata without setting environment, so check the field value. Treat other keys as
internal pipx state. If the function returns None, show the package’s usual installer-neutral advice.
Manual pages¶
To provide documentation via man pages on UNIX-like systems, ship them as data files. pipx exposes them the same way
it exposes apps.
# pyproject.toml
[tool.setuptools.data-files]
"share/man/man1" = [
"manpage.1",
]
# setup.cfg
[options.data_files]
share/man/man1 =
manpage.1
# setup.py
setup(
# other arguments here...
data_files=[("share/man/man1", ["manpage.1"])],
)
Warning
The data-files keyword is “discouraged” in the setuptools documentation,
but there is no alternative when man pages are a requirement.
# pyproject.toml
[tool.pdm.build]
source-includes = ["share"]
[tool.pdm.build.wheel-data]
data = [
{ path = "share/man/man1/*", relative-to = "." },
]
The manual page manpage.1 can then be read with man manpage after installing the package. For a real-world
example, see pycowsay’s setup.py.
Shell completions¶
A package can ship the completion script for its own command the same way it ships a man page. pipx picks up the three
directories a wheel installs completion scripts into and links each one under PIPX_COMPLETION_DIR (default
~/.local/share), so the completions arrive with the package and leave with it.
Shipped by the package |
Exposed to |
Loaded by |
|---|---|---|
|
|
bash |
|
|
fish |
|
|
zsh (with the directory on |
Declare them through the data files of your build backend, exactly as with man pages:
setup(
name="your-tool",
data_files=[
("share/bash-completion/completions", ["completions/your-tool"]),
("share/zsh/site-functions", ["completions/_your-tool"]),
("share/fish/vendor_completions.d", ["completions/your-tool.fish"]),
],
)
See Shell completions for how each shell loads these directories.
You can read more about entry points in the setuptools quickstart.