Contributing¶
Thanks for your interest in contributing to pipx!
Everyone who interacts with the pipx project via codebase, issue tracker, chat rooms, or otherwise is expected to follow the PSF Code of Conduct.
Submitting changes¶
Fork the GitHub repository.
Make a branch off of
mainand commit your changes to it.Add a changelog entry.
Submit a pull request to the
mainbranch on GitHub, referencing an open issue.
Changelog entries¶
The changelog at docs/changelog.rst is built by towncrier from news
fragments in the changelog.d/ directory. To add an entry, create a news fragment in that directory named
{number}.{type}.md, where {number} is the issue number, and {type} is one of feature, bugfix,
doc, removal, or misc.
For example, if your issue number is 1234 and it’s fixing a bug, then you would create changelog.d/1234.bugfix.md.
PRs can span multiple categories by creating multiple files: if you added a feature and deprecated/removed an old
feature for issue #5678, you would create changelog.d/5678.feature.md and changelog.d/5678.removal.md.
A changelog entry is meant for end users and should only contain details relevant to them. In order to maintain a consistent style, please keep the entry to the point, in sentence case, shorter than 80 characters, and in an imperative tone. An entry should complete the sentence “This change will …”. If one line is not enough, use a summary line in an imperative tone, followed by a description of the change in one or more paragraphs, each wrapped at 80 characters and separated by blank lines.
You don’t need to reference the pull request or issue number in a changelog entry, since towncrier will add a link using the number in the file name. Similarly, you don’t need to add your name to the entry, since that will be associated with the pull request.
Codebase Architecture¶
All source code lives under src/pipx/. The CLI entry point is main.py, which dispatches to command modules in
commands/.
flowchart TD
MAIN["main.py<br/>CLI parser + dispatch"] --> CMDS["commands/"]
CMDS --> INSTALL["install.py"]
CMDS --> RUN["run.py"]
CMDS --> UPGRADE["upgrade.py"]
CMDS --> INJECT["inject.py"]
CMDS --> LIST["list_packages.py"]
CMDS --> PIN["pin.py"]
CMDS --> INTERP_CMD["interpreter.py"]
CMDS --> OTHER["uninstall, uninject,<br/>reinstall, ensure_path,<br/>environment, run_pip"]
MAIN --> VENV["venv.py<br/>Venv + VenvContainer"]
MAIN --> PATHS["paths.py<br/>PIPX_HOME resolution"]
VENV --> SHARED["shared_libs.py<br/>shared pip venv"]
VENV --> INSPECT["venv_inspect.py<br/>metadata reader"]
VENV --> META["pipx_metadata_file.py<br/>JSON persistence"]
RUN --> SPEC["package_specifier.py<br/>PEP 508 parsing"]
RUN --> INTERP["interpreter.py<br/>Python resolution"]
INTERP --> STANDALONE["standalone_python.py<br/>download builds"]
style MAIN fill:#3f72af,color:#fff
style CMDS fill:#2a9d8f,color:#fff
style INSTALL fill:#2a9d8f,color:#fff
style RUN fill:#2a9d8f,color:#fff
style UPGRADE fill:#2a9d8f,color:#fff
style INJECT fill:#2a9d8f,color:#fff
style LIST fill:#2a9d8f,color:#fff
style PIN fill:#2a9d8f,color:#fff
style INTERP_CMD fill:#2a9d8f,color:#fff
style OTHER fill:#2a9d8f,color:#fff
style VENV fill:#7c4dff,color:#fff
style PATHS fill:#c78c20,color:#fff
style SHARED fill:#c78c20,color:#fff
style INSPECT fill:#7c4dff,color:#fff
style META fill:#7c4dff,color:#fff
style SPEC fill:#388e3c,color:#fff
style INTERP fill:#388e3c,color:#fff
style STANDALONE fill:#388e3c,color:#fff
Key modules¶
venv.py is the core abstraction. Venv wraps a single virtual environment (create, install, uninstall, upgrade)
and VenvContainer manages the collection under PIPX_HOME/venvs/. Both delegate pip operations to the shared
libraries venv managed by shared_libs.py.
paths.py resolves all directory locations from environment variables, platform defaults, and legacy fallback paths.
pipx_metadata_file.py serializes install options (spec, pip args, injected packages) to JSON inside each venv so
that upgrade and reinstall can reproduce the original install.
interpreter.py and standalone_python.py handle Python version resolution. When --fetch-python is missing
or always, pipx downloads a standalone build from python-build-standalone and caches it locally. The pipx interpreter
subcommands (list, prune, upgrade) manage these cached interpreters.
script.py parses PEP 723 inline metadata. run installs the declared dependencies in a cached temporary venv.
install sends both backends the same generated wheel and records the source path or URL.
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 various tasks.
tox defines environments in tox.toml which can be run with tox run -e ENV_NAME. Environment names can be
listed with tox list.
Install tox for pipx development:
$ python -m pip install --user tox
Tests are defined as tox environments. You can see all tox environments with
$ tox list
At the time of this writing, the output looks like this
default environments:
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.10 -> run tests with 3.10
type -> run the type checker
lint -> run pre-commit on the codebase
docs -> build documentation
man -> build man page
additional environments:
dev -> generate a DEV environment
docs-live -> serve documentation with live reload
zipapp -> build zipapp via shiv
Creating a developer environment¶
For developing the tool (and to attach to your IDE) we recommend creating a Python environment via tox run -e dev,
afterwards use the Python interpreter available under .tox/dev/bin/python.
Unit Tests¶
To run unit tests in Python 3.12, you can run
$ tox run -e 3.12
Tip
You can run a specific unit test by passing arguments to pytest, the test runner pipx uses:
$ tox run -e 3.10 -- -k EXPRESSION
EXPRESSION can be a test name, such as
$ tox run -e 3.10 -- -k test_uninstall
Coverage errors can usually be ignored when only running a subset of tests.
Running Unit Tests Offline¶
Running the unit tests requires a directory .pipx_tests/package_cache to be populated from a fixed list of package
distribution files (wheels or source files). If you have network access, tox automatically makes sure this
directory is populated (including downloading files if necessary) as a first step. Thus, if you are running the tests
with network access, you can ignore the rest of this section.
If, however, you wish to run tests offline without the need for network access, you can populate
.pipx_tests/package_cache yourself manually beforehand when you do have network access.
Lint Tests¶
Linting runs through pre-commit, wired into the lint tox environment:
$ tox run -e lint
The key hooks are ruff-check and ruff-format (lint and format Python), codespell (spelling), mdformat
(Markdown, wrapped at 120), yamlfmt (YAML), and pyproject-fmt (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¶
If the tests are modified such that a new package / version combination is pipx install``ed or ``pipx inject``ed that
wasn't used in other tests, then one must make sure it's added properly to the packages lists in
``testdata/tests_packages.
To accomplish this:
Edit
testdata/tests_packages/primary_packages.txtto add the new package(s) that you wish topipx installorpipx injectin the tests.
Then using Github workflows to generate all platforms in the Github CI:
Manually activate the Github workflow: Create tests package lists for offline tests
Download the artifact
listsand put the files from it intotestdata/tests_packages/
Finally, check-in the new or modified list files in the directory testdata/tests_packages
Testing pipx on Continuous Integration builds¶
Upon opening pull requests GitHub Actions will automatically trigger.
Building Documentation¶
The documentation is built with Sphinx. When updating pipx docs, make sure you are modifying the docs directory.
Build the HTML documentation with
$ tox run -e docs
This runs sphinx-build with -W --keep-going, so any warning fails the build. The CLI reference is generated
from pipx’s argparse parser by sphinx-argparse-cli, so it cannot drift from the actual commands. For a live-reloading
preview while editing, use tox run -e docs-live.
Releasing New pipx Versions¶
The release process for pipx is designed to be simple and fully automated with a single button press. The workflow automatically determines the next version based on changelog fragments, generates the changelog, creates the release commit, and publishes to PyPI.
Initiating a Release¶
Navigate to the Actions tab in the GitHub repository and select the Pre-release workflow. Click Run
workflow and choose the appropriate version bump strategy. The auto option intelligently determines whether a
minor or patch bump is needed by examining the types of changelog fragments present. If new features or removals exist,
it performs a minor version bump; otherwise, it increments the patch version. Alternatively, you can explicitly select
major, minor, or patch to control the version increment directly.
What Happens During Release¶
Once triggered, the pre-release workflow executes the scripts/release.py script which collects all changelog
fragments from the changelog.d/ directory and uses towncrier to generate the updated changelog. It then creates a
release commit with the message “Release {version}” and tags it with the version number. After running pre-commit hooks
to ensure formatting, both the commit and tag are pushed to the main branch.
The act of pushing a version tag (matching the pattern *.*.*) automatically triggers the main release workflow.
This workflow builds the project distribution files, publishes the package to PyPI using trusted publishing, creates a
GitHub release with auto-generated notes, and builds the zipapp using the minimum supported Python version before
uploading it to the GitHub release assets.
Version Calculation Examples¶
Starting from version 1.8.0, the version bump types produce the following results: auto with feature fragments
becomes 1.9.0, while auto with only bugfixes becomes 1.8.1. Selecting major explicitly jumps to
2.0.0, minor moves to 1.9.0, and patch increments to 1.8.1. This automation eliminates the need for
manual version management and ensures consistency across releases.