Skip to content

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

  1. Fork the GitHub repository.
  2. Make a branch off of main and commit your changes to it.
  3. Add a changelog entry.
  4. Submit a pull request to the main branch on GitHub, referencing an open issue.

Changelog entries

The CHANGELOG.md file 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-missing-python is passed, pipx downloads a standalone build from python-build-standalone and caches it locally. The pipx interpreter subcommands (list, prune, upgrade) manage these cached interpreters.

commands/run.py includes PEP 723 inline script metadata parsing. When you run a .py file, pipx scans for a # /// script block, extracts TOML-declared dependencies, and installs them into a cached temporary venv.

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.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
3.9      -> run tests with 3.9
lint     -> run pre-commit on the codebase
docs     -> build documentation
man      -> build man page

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.9 -- -k EXPRESSION

EXPRESSION can be a test name, such as

tox run -e 3.9 -- -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 is done via pre-commit, setting it up and running it can be done via tox by typing:

tox run -e lint

Installing or injecting new packages in tests

If the tests are modified such that a new package / version combination is pipx installed or pipx injected 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.txt to add the new package(s) that you wish to pipx install or pipx inject in 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 lists and put the files from it into testdata/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

pipx autogenerates API documentation, and also uses templates.

When updating pipx docs, make sure you are modifying the docs directory.

You can generate the documentation with

tox run -e docs

This will capture CLI documentation for any pipx argument modifications, as well as generate templates to the docs directory.

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.