Contributing¶
Thanks for your interest in contributing to pipx!
We expect everyone who interacts with the pipx project via codebase, issue tracker, chat rooms, or otherwise to follow the PSF Code of Conduct.
Submitting changes¶
Fork the GitHub repository.
Make a branch off
mainand commit your changes to it.Add a user-facing changelog entry, or apply
skip newsto a maintenance-only pull request.Submit a pull request to the
mainbranch onGitHub, referencing an open issue.
Changelog entries¶
towncrier builds the changelog from news fragments in changelog.d/. Name a
fragment +topic.type.rst before opening a pull request, for example +reset-eof.bugfix.rst. Existing numbered
names such as 1234.bugfix.rst remain valid. The release workflow gets the pull request number, the commit author,
and its co-authors from the squash commit and links them when it assembles the changelog.
Use feature for new capabilities and Python support additions or removals; either causes a minor version bump.
Use change for user-visible behavior changes, bugfix for corrections, doc for documentation, security
for security fixes, and performance for speed or resource improvements. deprecation announces a future removal;
removal describes a removed command or option; packaging covers user-visible distribution or installation
changes. Use breaking only for an incompatible behavior change that is not a Python support change.
One pull request may carry several fragments, including two of the same type: +reset-eof.bugfix.rst and
+reset-output.bugfix.rst are separate entries. Write each one as a concrete sentence about the effect for a pipx
user. Internal tests, dependency updates, and CI work need no entry; apply skip news instead. Bot pull requests
without a fragment receive that label automatically. If a bot supplies a fragment, the changelog check validates it.
Do not put a pull request link or author credit in the fragment; hand-written credits fail the changelog check. The release workflow appends the squash pull request link and credits the commit’s human author and co-authors in the RST changelog. GitHub Release notes use their handles. Keep each fragment wrapped to 120 columns; prek formats the generated changelog before the release commit.
Concepts¶
pipx manages one environment per installed application: a virtual environment under PIPX_HOME named after its
main package, plus an optional suffix. pipx inject adds injected packages to an existing environment. pipx
resolves PIPX_HOME and every other location from environment variables, platform defaults, and legacy fallback
paths (see Environment variables).
A backend installs packages into environments. The pip backend links every environment to one shared pip
environment through a .pth file; the uv backend calls uv venv and uv pip and needs no shared environment.
Each environment runs on an interpreter that pipx resolves from --python. When --fetch-python is
missing or always, pipx downloads a standalone build from python-build-standalone and caches it; pipx interpreter list, prune,
and upgrade manage those builds.
After an install, pipx exposes the package’s resources: its console and GUI scripts (apps), man pages, and shell
completions, linked into PIPX_BIN_DIR, PIPX_MAN_DIR, and PIPX_COMPLETION_DIR. Each environment’s
metadata records the install source, backend, pip arguments, interpreter, and exposed resources, so upgrade,
reinstall, and install-all can rebuild the same state.
pipx run uses the run cache instead: temporary environments keyed on the requirements and installer settings,
which expire after 14 days. A script with PEP 723 inline metadata works as a package: pipx run installs its
declared dependencies into a cached temporary environment, and pipx install hands either backend the same wheel
built from the script and records its path or URL.
Example: installing black. pipx install black resolves the default interpreter, creates the black
environment, and has the backend install black into it. pipx then exposes the black and blackd apps and
records the source black and its pip arguments in the metadata. A later pipx upgrade black reads that metadata,
upgrades the package with the same backend and arguments, exposes any apps the new version adds, and removes the links
of any it dropped.
Running pipx For Development¶
To develop pipx, either create a developer environment, or perform an
editable install:
$ python -m pip install -e .
$ python -m pipx --version
Running Tests¶
Setup¶
pipx uses tox for development, continuous integration testing, and other tasks.
tox list shows its environments; at the time of writing, the output looks like this:
default environments:
3.15 -> run tests with 3.15
3.14 -> run tests with 3.14
3.13 -> run tests with 3.13
3.12 -> run tests with 3.12
3.11 -> run tests with 3.11
3.15t -> run tests with 3.15t
docs -> build documentation
lint -> run pre-commit on the codebase
man -> build man page
type -> run the type checker
additional environments:
dev -> generate a DEV environment
docs-live -> serve documentation with live reload
zipapp -> build zipapp via shiv
Creating a developer environment¶
For development (and to attach your IDE), create a Python environment with tox run -e dev, then use the interpreter
at .tox/dev/bin/python.
Unit Tests¶
Run the unit tests on Python 3.15 with:
$ tox run -e 3.15
Tip
tox passes the arguments after -- to pytest, the test runner pipx uses, so
-k EXPRESSION selects tests by name:
$ tox run -e 3.15 -- -k test_uninstall
You can usually ignore coverage errors when you run only a subset of tests.
Running Unit Tests Offline¶
The unit tests need a .pipx_tests/package_cache directory populated from a fixed list of package distribution files
(wheels or source files). With network access, tox populates it as a first step, downloading files when needed, so
you can skip the rest of this section.
To run the tests without network access, populate .pipx_tests/package_cache beforehand, while you still have
network access.
Lint Tests¶
Linting runs through pre-commit, wired into the lint tox environment:
$ tox run -e lint
The main hooks are ruff-check and ruff-format (lint and format Python), codespell (spelling), mdformat
(Markdown, wrapped at 120), yamlfmt (YAML), pyproject-fmt (pyproject.toml), and flake8-lazy.
flake8-lazy keeps the __lazy_modules__ list at the top of each pipx module up to date. On Python 3.15 and later
(PEP 810) the modules it lists load on first use rather than at import, which shortens CLI startup; older Pythons
ignore the list. The hook rewrites the list itself, so when it fails, review and stage its change. A module whose import
has a side effect that must happen at startup (reading environment variables or terminal state) belongs in
standalone.lazy-exclude-modules under [tool.flake8-lazy] in pyproject.toml.
Type checking¶
Type checking uses ty and runs in its own tox environment rather than
pre-commit, since pre-commit.ci cannot install its heavier dependency set:
$ tox run -e type
A dedicated GitHub Actions job runs the same check on every pull request.
Installing or injecting new packages in tests¶
When a test installs or injects a package and version that no other test uses, add it to the package lists in
testdata/tests_packages:
Add the package to
testdata/tests_packages/primary_packages.txt.On each platform and Python version the tests run on, regenerate that platform’s list:
$ python scripts/list_test_packages.py testdata/tests_packages/primary_packages.txt testdata/tests_packages
Commit the new or modified list files in
testdata/tests_packages.
Testing pipx on Continuous Integration builds¶
GitHub Actions runs the checks when you open a pull request.
Building Documentation¶
Sphinx builds the documentation from the docs directory; edit the pages there. Build
the HTML documentation with:
$ tox run -e docs
This runs sphinx-build with -W --keep-going, so any warning fails the build. sphinx-argparse-cli generates the CLI reference from pipx’s argument parser, so it cannot
drift from the commands. For a live-reloading preview while editing, use tox run -e docs-live.
Releasing New pipx Versions¶
The protected pre-release workflow determines the next version from the fragment types, builds the changelog, runs
release-creator contributors to add a New Contributors subsection, formats the changelog with prek, and
prepares a patch. The repository-scoped App then pushes the release commit and annotated tag.
Initiating a Release¶
Run Pre-release from the Actions tab on main. Choose auto to calculate the version from the fragments, or
choose major, minor, or patch to override the bump. The input is a bump type, never a typed version number.
To preview the same inputs locally without pushing, run python scripts/release.py or python scripts/release.py
--bump minor from an installed release dependency group.
What Happens During Release¶
The tag starts the release workflow. It builds the sdist and wheel with locked build dependencies, and builds
pipx.pyz from the wheel and the runtime dependencies locked for the zipapp targets. In parallel it renders Markdown
notes from that tag’s RST changelog. Once the zipapp passes its smoke test, the distributions go to PyPI with trusted publishing. After PyPI has the
expected files, a single step creates the GitHub Release with the zipapp attached.
Retry a failed run with “Re-run failed jobs”, which uploads the same built files; the release job checks their SHA256
digests against PyPI before it writes the GitHub Release.
Version Calculation Examples¶
Starting from 1.8.0, auto with a feature becomes 1.9.0 and auto with only bug fixes becomes 1.8.1.
Selecting major yields 2.0.0, minor yields 1.9.0, and patch yields 1.8.1. Removing support for
an older Python is a feature here because earlier pipx releases remain installable on that interpreter.