############# 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 ``main`` and commit your changes to it. #. Add a changelog entry. #. Submit a pull request to the ``main`` branch 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/``. .. mermaid:: flowchart TD MAIN["main.py
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,
reinstall, ensure_path,
environment, run_pip"] MAIN --> VENV["venv.py
Venv + VenvContainer"] MAIN --> PATHS["paths.py
PIPX_HOME resolution"] VENV --> SHARED["shared_libs.py
shared pip venv"] VENV --> INSPECT["venv_inspect.py
metadata reader"] VENV --> META["pipx_metadata_file.py
JSON persistence"] RUN --> SPEC["package_specifier.py
PEP 508 parsing"] RUN --> INTERP["interpreter.py
Python resolution"] INTERP --> STANDALONE["standalone_python.py
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 :ref:`developer environment `, or perform an editable install: .. code-block:: console $ 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: .. code-block:: console $ python -m pip install --user tox Tests are defined as ``tox`` environments. You can see all tox environments with .. code-block:: console $ tox list At the time of this writing, the output looks like this .. code-block:: text 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: 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 .. code-block:: console $ tox run -e 3.12 .. tip:: You can run a specific unit test by passing arguments to pytest, the test runner pipx uses: .. code-block:: console $ tox run -e 3.10 -- -k EXPRESSION ``EXPRESSION`` can be a test name, such as .. code-block:: console $ 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: .. code-block:: console $ 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: .. code-block:: console $ 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.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 *********************** The documentation is built with Sphinx. When updating pipx docs, make sure you are modifying the ``docs`` directory. Build the HTML documentation with .. code-block:: console $ 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.