Full Code of pypa/pipx for AI

main 60ecaffde83d cached
139 files
885.5 KB
274.8k tokens
550 symbols
1 requests
Download .txt
Showing preview only (930K chars total). Download the full file or copy to clipboard to get everything.
Repository: pypa/pipx
Branch: main
Commit: 60ecaffde83d
Files: 139
Total size: 885.5 KB

Directory structure:
gitextract_awsmj108/

├── .deepsource.toml
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yaml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SECURITY.md
│   ├── config.yml
│   ├── dependabot.yaml
│   ├── release.yml
│   └── workflows/
│       ├── create_tests_package_lists.yml
│       ├── exhaustive_package_test.yml
│       ├── pre-release.yml
│       ├── release.yml
│       └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .pre-commit-hooks.yaml
├── .readthedocs.yml
├── LICENSE
├── changelog.d/
│   ├── .gitignore
│   ├── 1443.bugfix.md
│   ├── 1508.bugfix.md
│   ├── 1602.bugfix.md
│   └── 1718.bugfix.md
├── docs/
│   ├── README.md
│   ├── changelog.md
│   ├── contributing.md
│   ├── explanation/
│   │   ├── comparisons.md
│   │   ├── how-pipx-works.md
│   │   ├── index.md
│   │   └── making-packages-compatible.md
│   ├── how-to/
│   │   ├── configure-paths.md
│   │   ├── index.md
│   │   ├── inject-packages.md
│   │   ├── install-pipx.md
│   │   ├── move-installation.md
│   │   ├── pin-packages.md
│   │   ├── run-scripts.md
│   │   ├── shell-completions.md
│   │   ├── troubleshoot.md
│   │   ├── upgrade-pipx.md
│   │   └── use-with-pre-commit.md
│   ├── index.md
│   ├── reference/
│   │   ├── environment-variables.md
│   │   ├── examples.md
│   │   ├── index.md
│   │   └── programs-to-try.md
│   └── tutorial/
│       ├── getting-started.md
│       ├── index.md
│       ├── install-applications.md
│       └── run-applications.md
├── get-pipx.py
├── mkdocs.yml
├── pyproject.toml
├── scripts/
│   ├── gen_doc_pages.py
│   ├── generate_man.py
│   ├── list_test_packages.py
│   ├── migrate_pipsi_to_pipx.py
│   ├── release.py
│   ├── templates/
│   │   └── docs.md
│   ├── test_packages_support.py
│   └── update_package_cache.py
├── src/
│   └── pipx/
│       ├── __init__.py
│       ├── __main__.py
│       ├── animate.py
│       ├── colors.py
│       ├── commands/
│       │   ├── __init__.py
│       │   ├── common.py
│       │   ├── ensure_path.py
│       │   ├── environment.py
│       │   ├── inject.py
│       │   ├── install.py
│       │   ├── interpreter.py
│       │   ├── list_packages.py
│       │   ├── pin.py
│       │   ├── reinstall.py
│       │   ├── run.py
│       │   ├── run_pip.py
│       │   ├── uninject.py
│       │   ├── uninstall.py
│       │   └── upgrade.py
│       ├── constants.py
│       ├── emojis.py
│       ├── interpreter.py
│       ├── main.py
│       ├── package_specifier.py
│       ├── paths.py
│       ├── pipx_metadata_file.py
│       ├── shared_libs.py
│       ├── standalone_python.py
│       ├── util.py
│       ├── venv.py
│       ├── venv_inspect.py
│       └── version.pyi
├── testdata/
│   ├── empty_project/
│   │   ├── README.md
│   │   ├── empty_project/
│   │   │   ├── __init__.py
│   │   │   └── main.py
│   │   └── pyproject.toml
│   ├── pipx_metadata_multiple_errors.json
│   ├── standalone_python_index_20250818.json
│   ├── standalone_python_index_20250828.json
│   ├── test_package_specifier/
│   │   └── local_extras/
│   │       ├── repeatme/
│   │       │   ├── __init__.py
│   │       │   └── main.py
│   │       └── setup.py
│   └── tests_packages/
│       └── README.md
├── tests/
│   ├── conftest.py
│   ├── helpers.py
│   ├── package_info.py
│   ├── test_animate.py
│   ├── test_common.py
│   ├── test_completions.py
│   ├── test_emojis.py
│   ├── test_environment.py
│   ├── test_inject.py
│   ├── test_install.py
│   ├── test_install_all.py
│   ├── test_install_all_packages.py
│   ├── test_interpreter.py
│   ├── test_list.py
│   ├── test_main.py
│   ├── test_package_specifier.py
│   ├── test_pin.py
│   ├── test_pipx_metadata_file.py
│   ├── test_reinstall.py
│   ├── test_reinstall_all.py
│   ├── test_run.py
│   ├── test_runpip.py
│   ├── test_shared_libs.py
│   ├── test_standalone_interpreter.py
│   ├── test_uninject.py
│   ├── test_uninstall.py
│   ├── test_uninstall_all.py
│   ├── test_unpin.py
│   ├── test_upgrade.py
│   ├── test_upgrade_all.py
│   └── test_upgrade_shared.py
└── tox.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .deepsource.toml
================================================
version = 1

test_patterns = ["tests/**"]

[[analyzers]]
name = "python"
enabled = true

  [analyzers.meta]
  runtime_version = "3.x.x"

[[transformers]]
name = "black"
enabled = true


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to `pipx`

Thank you for your interest in contributing to pipx! There are many ways to contribute, and we appreciate all of them.
As a reminder, all contributors are expected to follow the [PSF Code of Conduct][coc].

## Development Documentation

Our [development documentation](https://pipx.pypa.io/latest/contributing/) contains details on how to get started with
contributing to `pipx`, and details of our development processes.

[coc]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md


================================================
FILE: .github/FUNDING.yaml
================================================
tidelift: "pypi/pipx"


================================================
FILE: .github/ISSUE_TEMPLATE/bug.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---

**Issue**

Describe what's the expected behavior and what you're observing.

**Environment**

Provide at least:

- OS:
- Shell:
- Python version and path:
- `pipx --version` output:

**Output of the failing command**

Make sure to run the command with `--verbose`:


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: true # default
contact_links:
  - name: "💬 pypa/pipx @ Discord"
    url: https://discord.gg/pypa
    about: Chat with the devs
  - name: 🤷💻🤦 Discourse
    url: https://discuss.python.org/c/packaging
    about: |
      Please ask typical Q&A here: general ideas for Python packaging, questions about structuring projects and so on
  - name: 📝 PSF Code of Conduct
    url: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
    about: ❤ Be nice to other members of the community. ☮ Behave.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an enhancement for this project
title: ''
labels: enhancement
assignees: ''
---

**What's the problem this feature will solve?**

<!-- What are you trying to do, that you are unable to achieve with pipx as it currently stands? -->

**Describe the solution you'd like**

<!-- Clear and concise description of what you want to happen. -->

<!-- Provide examples of real world use cases that this would enable and how it solves the problem described above. -->

**Alternative Solutions**

<!-- Have you tried to workaround the problem using pipx or other tools? Or a different approach to solving this issue? -->

**Additional context**

<!-- Add any other context, links, etc. about the feature here. -->


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### Thanks for contributing, make sure you address all the checklists (for details on how see [development documentation](https://pipx.pypa.io/latest/contributing/))

- [ ] ran the linter to address style issues (`pre-commit run --all-files`)
- [ ] wrote descriptive pull request text
- [ ] ensured there are test(s) validating the fix
- [ ] added news fragment in `changelog.d/` folder
- [ ] updated/extended the documentation


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| 1.7.0+  | :white_check_mark: |
| < 1.7.0 | :x:                |

## Reporting a Vulnerability

To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift
will coordinate the fix and disclosure.


================================================
FILE: .github/config.yml
================================================
rtd:
  project: pipx


================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    authors:
      - dependabot[bot]
      - pre-commit-ci[bot]
      - github-actions[bot]


================================================
FILE: .github/workflows/create_tests_package_lists.yml
================================================
name: Create tests package lists for offline tests
on:
  workflow_dispatch:
concurrency:
  group: create-tests-package-lists-${{ github.ref }}
  cancel-in-progress: true
jobs:
  create_package_lists:
    name: Create package lists
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest]
        python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
        include:
          - os: macos-latest
            python-version: "3.13"
          - os: windows-latest
            python-version: "3.13"
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
      - name: Install nox
        run: python -m pip install nox
      - name: Create lists
        run: nox --non-interactive --session create_test_package_list-${{ matrix.python-version }} -- ./new_tests_packages
      - name: Store reports as artifacts
        uses: actions/upload-artifact@v7
        with:
          name: lists-${{ matrix.os }}-${{ matrix.python-version }}
          path: ./new_tests_packages


================================================
FILE: .github/workflows/exhaustive_package_test.yml
================================================
name: Exhaustive Package Test (slow)
on:
  workflow_dispatch:
concurrency:
  group: exhaustive-package-test-${{ github.ref }}
  cancel-in-progress: true
jobs:
  test_all_packages:
    name: Exhaustive Package Test
    continue-on-error: true
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.12", "3.11", "3.10", "3.9"]
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
      - name: Install nox
        run: python -m pip install nox
      - name: Execute Tests
        continue-on-error: true
        run: nox --non-interactive --session test_all_packages-${{ matrix.python-version }}
      - name: Store reports as artifacts
        uses: actions/upload-artifact@v7
        with:
          name: reports-raw-${{ matrix.os }}-${{ matrix.python-version }}
          path: reports
  report_all_packages:
    name: Collate test reports
    needs: test_all_packages
    runs-on: ubuntu-latest
    steps:
      - name: Get report artifacts
        uses: actions/download-artifact@v8
        with:
          pattern: reports-raw-*
          merge-multiple: true
      - name: Collate reports
        run: |
          ls # DEBUG
          mkdir reports
          cat all_packages_report_legend.txt > all_nodeps_reports_lf.txt
          cat all_packages_nodeps_report_* >> all_nodeps_reports_lf.txt
          tr -d '\r' < all_nodeps_reports_lf.txt > reports/all_nodeps_reports.txt
          cat all_packages_nodeps_errors_* > all_nodeps_errors_lf.txt
          tr -d '\r' < all_nodeps_errors_lf.txt > reports/all_nodeps_errors.txt
          cat all_packages_report_legend.txt > all_deps_reports_lf.txt
          cat all_packages_deps_report_* >> all_deps_reports_lf.txt
          tr -d '\r' < all_deps_reports_lf.txt > reports/all_deps_reports.txt
          cat all_packages_deps_errors_* > all_deps_errors_lf.txt
          tr -d '\r' < all_deps_errors_lf.txt > reports/all_deps_errors.txt
      - name: Store collated and raw reports as artifacts
        uses: actions/upload-artifact@v7
        with:
          name: reports-final
          path: reports


================================================
FILE: .github/workflows/pre-release.yml
================================================
name: Pre-release
on:
  workflow_dispatch:
    inputs:
      bump:
        description: "Version bump type"
        required: true
        type: choice
        options:
          - auto
          - major
          - minor
          - patch
        default: auto
env:
  default-python: "3.13"
jobs:
  pre-release:
    runs-on: ubuntu-latest
    environment: release
    permissions:
      contents: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          token: ${{ secrets.GH_RELEASE_TOKEN }}
      - name: Set up Python ${{ env.default-python }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ env.default-python }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install GitPython packaging towncrier pre-commit
      - name: Configure git identity
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
      - name: Generate changelog, commit, tag, and push
        run: python scripts/release.py --version "${{ inputs.bump }}"


================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  push:
    tags:
      - "*.*.*"
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
env:
  default-python: "3.14"
jobs:
  pypi-publish:
    name: Publish pipx to PyPI
    runs-on: ubuntu-latest
    environment:
      name: release
      url: https://pypi.org/p/pipx
    permissions:
      id-token: write
    steps:
      - name: Checkout ${{ github.ref_name }}
        uses: actions/checkout@v6
        with:
          ref: "${{ github.ref_name }}"
      - name: Set up Python ${{ env.default-python }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ env.default-python }}
          cache: "pip"
      - name: Build sdist and wheel
        run: |
          pip install build
          python -m build
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@v1.13.0
  create-release:
    name: Create a release on GitHub's UI
    needs: pypi-publish
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
      - name: Create release
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          tag_name: "${{ github.ref_name }}"
  upload-zipapp:
    name: Upload zipapp to GitHub Release
    needs: create-release
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout ${{ github.ref_name }}
        uses: actions/checkout@v6
        with:
          ref: "${{ github.ref_name }}"
      - name: Set up Python ${{ env.default-python }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ env.default-python }}
          cache: "pip"
      - name: Install tox
        run: pip install tox
      - name: Build zipapp
        run: tox run -e zipapp
      - name: Upload to release
        uses: softprops/action-gh-release@v2
        with:
          files: pipx.pyz
          tag_name: "${{ github.ref_name }}"


================================================
FILE: .github/workflows/tests.yml
================================================
name: 🧪 tests
on:
  workflow_dispatch:
  push:
    branches:
      - "main"
  pull_request:
  schedule:
    - cron: "0 8 * * *"
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
jobs:
  test:
    name: 🧪 test ${{ matrix.py }} - ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 40
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-24.04]
        py: ["3.13", "3.12", "3.11", "3.10", "3.9"]
        include:
          - os: windows-2025
            py: "3.13"
          - os: macos-15
            py: "3.13"
    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: 🚀 Install uv
        uses: astral-sh/setup-uv@v7
      - name: 🐍 Setup Python ${{ matrix.py }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.py }}
      - name: 📦 Install tox
        run: uv tool install --python-preference only-managed --python 3.13 "tox>=4.45" --with tox-uv
      - name: Persistent .pipx_tests/package_cache
        uses: actions/cache@v5
        with:
          path: ${{ github.workspace }}/.pipx_tests/package_cache/${{ matrix.py }}
          key: pipx-tests-package-cache-${{ runner.os }}-${{ matrix.py }}
      - name: 🏗️ Setup test suite
        run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.py }}
      - name: 📦 Seed package cache
        run: tox exec -e ${{ matrix.py }} -- python scripts/update_package_cache.py testdata/tests_packages .pipx_tests/package_cache/
      - name: 🏃 Run test suite
        run: tox run --skip-pkg-install -e ${{ matrix.py }}
        timeout-minutes: 30
        env:
          PYTEST_ADDOPTS: "-vv --durations=20"
  man:
    name: 📖 Build man page
    runs-on: ubuntu-24.04
    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v6
      - name: 🚀 Install uv
        uses: astral-sh/setup-uv@v7
      - name: 📦 Install tox
        run: uv tool install --python-preference only-managed --python 3.13 "tox>=4.45" --with tox-uv
      - name: 📖 Build man page
        run: tox run -e man
      - name: Show man page
        run: man -l pipx.1
  zipapp:
    name: 📦 Build zipapp
    runs-on: ubuntu-24.04
    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v6
      - name: 🐍 Setup Python 3.9
        uses: actions/setup-python@v6
        with:
          python-version: "3.9"
      - name: 📦 Build zipapp
        run: |
          pip install shiv
          shiv -c pipx -o ./pipx.pyz .
          ./pipx.pyz --version
      - name: Test zipapp by installing black
        run: python ./pipx.pyz install black
      - uses: actions/upload-artifact@v7
        with:
          name: pipx.pyz
          path: pipx.pyz
          retention-days: 3


================================================
FILE: .gitignore
================================================
/.*_cache
/build
/dist
/src/pipx/version.py
/noxfile.py
/.nox
*.py[co]
__pycache__
/site
/.coverage*
/.pipx_tests
/testdata/tests_packages/*.txt
/pipx.pyz
*.egg-info
build
*.whl
/pipx.1
/docs/_draft_changelog.md


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: end-of-file-fixer
      - id: check-added-large-files
      - id: trailing-whitespace
      - id: check-merge-conflict
      - id: check-case-conflict
  - repo: https://github.com/google/yamlfmt
    rev: v0.21.0
    hooks:
      - id: yamlfmt
  - repo: https://github.com/hukkin/mdformat
    rev: 0.7.22
    hooks:
      - id: mdformat
        args: ["--wrap", "120"]
        additional_dependencies:
          - mdformat-mkdocs
          - mdformat-gfm
          - mdformat-gfm-alerts
          - mdformat-footnote
          - mdformat-config
          - mdformat-front-matters
        exclude: ^(docs/changelog\.md|testdata)
  - repo: https://github.com/tox-dev/pyproject-fmt
    rev: v2.20.0
    hooks:
      - id: pyproject-fmt
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.6
    hooks:
      - id: ruff-check
        args: ["--fix", "--unsafe-fixes", "--show-fixes", "--exit-non-zero-on-fix"]
      - id: ruff-format
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.19.1
    hooks:
      - id: mypy
        args: ['--warn-unused-ignores', '--strict-equality', '--no-implicit-optional', '--check-untyped-defs']
        exclude: 'testdata/test_package_specifier/local_extras/setup.py'
        additional_dependencies:
          - "mkdocs-gen-files"
          - "nox"
          - "packaging>=20"
          - "platformdirs>=2.1"
          - "tomli; python_version < '3.11'"
  # Configuration for codespell is in pyproject.toml
  - repo: https://github.com/codespell-project/codespell
    rev: v2.4.2
    hooks:
      - id: codespell
        additional_dependencies:
          - tomli
        exclude: ^testdata


================================================
FILE: .pre-commit-hooks.yaml
================================================
- id: pipx
  name: pipx
  entry: pipx run
  require_serial: true
  language: python
  minimum_pre_commit_version: '2.9.2'


================================================
FILE: .readthedocs.yml
================================================
version: 2
build:
  os: ubuntu-22.04
  tools:
    python: "3.13"
  commands:
    - pip install "tox>=4.45" tox-uv
    - tox run -e docs


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Chad Smith and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: changelog.d/.gitignore
================================================
!.gitignore


================================================
FILE: changelog.d/1443.bugfix.md
================================================
Fix `--global` flag being silently ignored when placed before the subcommand


================================================
FILE: changelog.d/1508.bugfix.md
================================================
Respect `--quiet` flag in `install` and suppress animations and success messages


================================================
FILE: changelog.d/1602.bugfix.md
================================================
Catch missing Python interpreter in `upgrade` and show helpful error instead of crashing


================================================
FILE: changelog.d/1718.bugfix.md
================================================
Prevent data loss by rejecting relative paths in `uninstall`, `reinstall`, and other package-name arguments


================================================
FILE: docs/README.md
================================================
<p align="center">
<a href="https://pipx.pypa.io">
<img align="center" src="https://github.com/pypa/pipx/raw/main/logo.svg" width="200"/>
</a>
</p>

# pipx — Install and Run Python Applications in Isolated Environments

<p align="center">
<a href="https://github.com/pypa/pipx/raw/main/pipx_demo.gif">
<img src="https://github.com/pypa/pipx/raw/main/pipx_demo.gif"/>
</a>
</p>

<p align="center">
<a href="https://github.com/pypa/pipx/actions/workflows/tests.yml">
<img src="https://github.com/pypa/pipx/actions/workflows/tests.yml/badge.svg?event=push&branch=main" alt="image" /></a> <a href="https://badge.fury.io/py/pipx"><img src="https://badge.fury.io/py/pipx.svg" alt="PyPI version"></a> <a href="https://badge.fury.io/py/pipx"><img src="https://static.pepy.tech/badge/pipx"></a>

</p>

**Documentation**: <https://pipx.pypa.io>

**Source Code**: <https://github.com/pypa/pipx>

_For comparison to other tools including pipsi, see
[Comparison to Other Tools](https://pipx.pypa.io/stable/explanation/comparisons/)._

## Overview: What is `pipx`?

pipx is a tool to help you install and run end-user applications written in Python. It's roughly similar to macOS's
`brew`, JavaScript's [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b), and
Linux's `apt`.

It's closely related to pip. In fact, it uses pip, but is focused on installing and managing Python packages that can be
run from the command line directly as applications.

### Features

`pipx` enables you to

- expose CLI entrypoints of packages ("apps") installed to isolated environments with the `install` command,
    guaranteeing no dependency conflicts and clean uninstalls;
- easily list, upgrade, and uninstall packages that were installed with pipx; and
- run the latest version of a Python application in a temporary environment with the `run` command.

Best of all, pipx runs with regular user permissions, never calling `sudo pip install`.

## Install pipx

### On macOS

```
brew install pipx
pipx ensurepath
```

### On Linux

```
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

### On Windows

```
scoop install pipx
pipx ensurepath
```

For more detailed installation instructions, see the
[full documentation](https://pipx.pypa.io/stable/how-to/install-pipx/).

## Quick Start

Install an application globally:

```
pipx install pycowsay
pycowsay mooo
```

Run an application without installing:

```
pipx run pycowsay moo
```

See the [full documentation](https://pipx.pypa.io/stable/) for more details.

## Contributing

Issues and Pull Requests are definitely welcome! Check out [Contributing](https://pipx.pypa.io/stable/contributing/) to
get started. Everyone who interacts with the pipx project via codebase, issue tracker, chat rooms, or otherwise is
expected to follow the [PSF Code of Conduct](https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md).


================================================
FILE: docs/changelog.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

This project uses [*towncrier*](https://towncrier.readthedocs.io/) for keeping the changelog. DO NOT commit any changes to this file.
{% include '_draft_changelog.md' ignore missing %}

<!-- towncrier release notes start -->

## [1.10.0](https://github.com/pypa/pipx/tree/1.10.0) - 2026-03-18

### Features

- Add `--with` flag to `pipx run` to allow injecting dependencies ([#1607](https://github.com/pypa/pipx/issues/1607))
- add more specific directions in the logs towards a resolution if you have a space in the PIX_HOME path ([#1634](https://github.com/pypa/pipx/issues/1634))

### Bugfixes

- Fixed upgrade command failing when package name includes extras (e.g., `pipx upgrade "coverage[toml]"`). ([#925](https://github.com/pypa/pipx/issues/925))
- Fix run command with bash substitution (e.g. `pipx run <(pbpaste)`) ([#1293](https://github.com/pypa/pipx/issues/1293))
- Allow `pipx runpip` to split single string arguments. ([#1520](https://github.com/pypa/pipx/issues/1520))
- Fix handling of shared libraries when the original Python interpreter is removed on Windows by detecting stale venv references and recreating the shared libraries with the current Python. ([#1723](https://github.com/pypa/pipx/issues/1723))

### Misc

- [#1638](https://github.com/pypa/pipx/issues/1638), [#1731](https://github.com/pypa/pipx/issues/1731)


## [1.9.0](https://github.com/pypa/pipx/tree/1.9.0) - 2026-03-17

### Features

- Add completion choices for `pipx environment --value`. ([#1498](https://github.com/pypa/pipx/issues/1498))

### Bugfixes

- Ignore recursive symlink loops in PIPX_BIN_DIR. ([#1592](https://github.com/pypa/pipx/issues/1592))
- `pipx reinstall`: An exception will now be raised if package is pinned. ([#1611](https://github.com/pypa/pipx/issues/1611))
- Stop `pipx run` from leaving bad temporary venvs when first installation was unsuccessful. ([#1709](https://github.com/pypa/pipx/issues/1709))


## [1.8.0](https://github.com/pypa/pipx/tree/1.8.0) - 2025-09-30

### Features

- Rename environmental variable `USE_EMOJI` to `PIPX_USE_EMOJI`. ([#1395](https://github.com/pypa/pipx/issues/1395))
- Add `--all-shells` flag to `pipx ensurepath`. ([#1585](https://github.com/pypa/pipx/issues/1585))
- Add support for Python 3.13 ([#1647](https://github.com/pypa/pipx/issues/1647))

### Bugfixes

- On Windows, no longer overwrite existing files on upgrade if source and destination are the same ([#683](https://github.com/pypa/pipx/issues/683))
- Update the logic of finding python interpreter such that `--fetch-missing-python` works on Windows ([#1521](https://github.com/pypa/pipx/issues/1521))
- Fix no message displayed when no packages are upgraded with `upgrade-all`. ([#1565](https://github.com/pypa/pipx/issues/1565))
- Fix incorrect order of flags when using `pipx upgrade`. ([#1610](https://github.com/pypa/pipx/issues/1610))
- Update the archive name of build of Python for Windows ([#1630](https://github.com/pypa/pipx/issues/1630))
- Set up standalone python fetching to use checksums directly from the GitHub API. ([#1652](https://github.com/pypa/pipx/issues/1652))
- Fix running a script with explicitly empty ``dependencies = []``. ([#1658](https://github.com/pypa/pipx/issues/1658))

### Improved Documentation

- Fix `/changelog/` and `/contributing/` docs URLs ([#1540](https://github.com/pypa/pipx/issues/1540))


## [1.7.1](https://github.com/pypa/pipx/tree/1.7.1) - 2024-08-23

### Bugfixes

- Use minimum supported Python to build zipapp in release action such that `tomli` is included in it. ([#1514](https://github.com/pypa/pipx/issues/1514))


## [1.7.0](https://github.com/pypa/pipx/tree/1.7.0) - 2024-08-22

### Features

- Add a `--prepend` option to the `pipx ensurepath` command to allow prepending `pipx`'s location to `PATH` rather than appending to it. This is useful when you want to prioritize `pipx`'s executables over other executables in your `PATH`. ([#1451](https://github.com/pypa/pipx/issues/1451))
- List `PIPX_GLOBAL_[HOME|BIN_DIR|MAN_DIR]` in `pipx environment`. ([#1492](https://github.com/pypa/pipx/issues/1492))

### Bugfixes

- Introduce `PIPX_HOME_ALLOW_SPACE` environment variable, to silence the spaces in pipx home path warning ([#1320](https://github.com/pypa/pipx/issues/1320))
- Fix passing constraints file path into `pipx install` operation via `pip` args ([#1389](https://github.com/pypa/pipx/issues/1389))
- Add help messages for `pipx pin` and `pipx unpin` commands. ([#1438](https://github.com/pypa/pipx/issues/1438))
- Stop `pipx install --global` from installing files in `~/.local`. ([#1475](https://github.com/pypa/pipx/issues/1475))
- Fix installation abortion on multiple packages when one or more are already installed. ([#1509](https://github.com/pypa/pipx/issues/1509))

### Improved Documentation

- Move all documentation files to the `docs` directory. ([#1479](https://github.com/pypa/pipx/issues/1479))


## [1.6.0](https://github.com/pypa/pipx/tree/1.6.0) - 2024-06-01


### Features

- Add `install-all` command to install packages according to spec metadata file. ([#687](https://github.com/pypa/pipx/issues/687))
- Introduce `pipx pin` and `pipx unpin` commands, which can be used to pin or unpin the version
  of an installed package, so it will not be upgraded by `pipx upgrade` or `pipx upgrade-all`. ([#891](https://github.com/pypa/pipx/issues/891))
- Add a new option `--pinned` to `pipx list` command for listing pinned packages only. ([#891](https://github.com/pypa/pipx/issues/891))
- Add `pipx interpreter upgrade` command to upgrade local standalone python in micro/patch level ([#1249](https://github.com/pypa/pipx/issues/1249))
- Add `--requirement` option to `inject` command to read list of packages from a text file. ([#1252](https://github.com/pypa/pipx/issues/1252))
- Add `pipx upgrade-shared` command, to create/upgrade shared libraries as a standalone command. ([#1316](https://github.com/pypa/pipx/issues/1316))
- Allow `upgrade` command to accept multiple packages as arguments. ([#1336](https://github.com/pypa/pipx/issues/1336))
- Support Python version for `--python` arg when py launcher is not available ([#1342](https://github.com/pypa/pipx/issues/1342))
- Make `install-all` gather errors in batch ([#1348](https://github.com/pypa/pipx/issues/1348))

### Bugfixes

- Resolve the `DEFAULT_PYTHON` to the actual absolute path ([#965](https://github.com/pypa/pipx/issues/965))
- Fix error log overwrite for "-all" batch operations. ([#1132](https://github.com/pypa/pipx/issues/1132))
- Do not reinstall already injected packages without `--force` being passed. ([#1300](https://github.com/pypa/pipx/issues/1300))
- Only show `--python` and `--force` flag warning if both flags are present ([#1304](https://github.com/pypa/pipx/issues/1304))
- Don't allow paths to be passed into `pipx reinstall`, as this might wreak havoc. ([#1324](https://github.com/pypa/pipx/issues/1324))
- Make the Python `venv` module arguments work with `upgrade --install` ([#1344](https://github.com/pypa/pipx/issues/1344))
- Fix version check for standalone python ([#1349](https://github.com/pypa/pipx/issues/1349))
- Validate package(s) argument should not be path(s). ([#1354](https://github.com/pypa/pipx/issues/1354))
- Validate whether a package is an URL correctly. ([#1355](https://github.com/pypa/pipx/issues/1355))
- Support python3.8 for standalone python builds ([#1375](https://github.com/pypa/pipx/issues/1375))
- Install specified version of `--preinstall` dependency instead of latest version ([#1377](https://github.com/pypa/pipx/issues/1377))
- Move `--global` option into shared parser, such that it can be passed after the subcommand, for example `pipx ensurepath --global`. ([#1397](https://github.com/pypa/pipx/issues/1397))
- Fix discovery of a `pipx run` entry point if a local path was given as package. ([#1422](https://github.com/pypa/pipx/issues/1422))

### Improved Documentation

- Create a dedicated section for manual pages and add an example with `pdm-backend`. ([#1312](https://github.com/pypa/pipx/issues/1312))
- Add example, test and cli help description how to install multiple packages with the --preinstall flag ([#1321](https://github.com/pypa/pipx/issues/1321))
- Refine docs generation script and template. ([#1325](https://github.com/pypa/pipx/issues/1325))
- Add a note about sourcing the shell config file for `ensure_path` ([#1346](https://github.com/pypa/pipx/issues/1346))


## [1.5.0](https://github.com/pypa/pipx/tree/1.5.0) - 2024-03-29


### Features

- Add `--global` option to `pipx` commands.
      - This will run the action in a global scope and affect environment for all system users. ([#754](https://github.com/pypa/pipx/issues/754))
- Add a `--fetch-missing-python` flag to all commands that accept a `--python` flag.
      - When combined, this will automatically download a standalone copy of the requested python version if it's not already available on the user's system. ([#1242](https://github.com/pypa/pipx/issues/1242))
- Add commands to list and prune standalone interpreters ([#1248](https://github.com/pypa/pipx/issues/1248))
- Revert platform-specific directories on MacOS and Windows
      - They were leading to a lot of issues with Windows sandboxing and spaces in shebangs on MacOS. ([#1257](https://github.com/pypa/pipx/issues/1257))
- Add `--install` option to `pipx upgrade` command.
      - This will install the package given as argument if it is not already installed. ([#1262](https://github.com/pypa/pipx/issues/1262))

### Bugfixes

- Correctly resolve home directory in pipx directory environment variables. ([#94](https://github.com/pypa/pipx/issues/94))
- Pass through `pip` arguments when upgrading shared libraries. ([#964](https://github.com/pypa/pipx/issues/964))
- Fix installation issues when files in the working directory interfere with venv creation process. ([#1091](https://github.com/pypa/pipx/issues/1091))
- Report correct filename in tracebacks with `pipx run <scriptname>` ([#1191](https://github.com/pypa/pipx/issues/1191))
- Let self-managed pipx uninstall itself on windows again. ([#1203](https://github.com/pypa/pipx/issues/1203))
- Fix path resolution for python executables looked up in PATH on windows. ([#1205](https://github.com/pypa/pipx/issues/1205))
- Display help message when `pipx install` is run without arguments. ([#1266](https://github.com/pypa/pipx/issues/1266))
- Fix crashes due to superfluous `-q ` flags by discarding exceeding values ([#1283](https://github.com/pypa/pipx/issues/1283))

### Improved Documentation

- Update the completion instructions for zipapp users. ([#1072](https://github.com/pypa/pipx/issues/1072))
- Update the example for running scripts with dependencies. ([#1227](https://github.com/pypa/pipx/issues/1227))
- Update the docs for package developers on the use of configuration using pyproject.toml ([#1229](https://github.com/pypa/pipx/issues/1229))
- Add installation instructions for Fedora ([#1239](https://github.com/pypa/pipx/issues/1239))
- Update the examples for installation from local dir ([#1277](https://github.com/pypa/pipx/issues/1277))
- Fix inconsistent wording in `pipx install` command description. ([#1307](https://github.com/pypa/pipx/issues/1307))

### Deprecations and Removals

- Deprecate `--skip-maintenance` flag of `pipx list`; maintenance is now never executed there ([#1256](https://github.com/pypa/pipx/issues/1256))

### Misc

- [#1296](https://github.com/pypa/pipx/issues/1296)


## [1.4.3](https://github.com/pypa/pipx/tree/1.4.3) - 2024-01-16


### Bugfixes

- Autofix python version for pylauncher, when version is provided prefixed with `python` ([#1150](https://github.com/pypa/pipx/issues/1150))
- Support building pipx wheels with setuptools-scm<7, such as on FreeBSD. ([#1208](https://github.com/pypa/pipx/issues/1208))

### Improved Documentation

- Provide useful error messages when unresolvable python version is passed ([#1150](https://github.com/pypa/pipx/issues/1150))
- Introduce towncrier for managing the changelog ([#1161](https://github.com/pypa/pipx/issues/1161))
- Add workaround for using pipx applications in shebang under macOS ([#1198](https://github.com/pypa/pipx/issues/1198))


## [1.4.2](https://github.com/pypa/pipx/tree/1.4.2)

### Features

- Allow skipping maintenance tasks during list command
- Raise more user friendly error when provided `--python` version is not found
- Update `pipx run` on scripts using `/// script` and no `run` table following the updated version of PEP 723 (#1180)

### Bugfixes

- Include `tomli` into `pipx.pyz` (zipapp) so that it can be executed with Python 3.10 or earlier (#1142)
- Fix resolving the python executable path on linux
- `pipx run`: Verify whether the script name provided is a file before running it
- Avoid repeated exception logging in a few rare cases (#1192)

## [1.4.1](https://github.com/pypa/pipx/tree/1.4.1)

### Bugfixes

- Set default logging level to WARNING, so debug log messages won't be shown without passing additional flags such as `--verbose`

## [1.4.0](https://github.com/pypa/pipx/tree/1.4.0)

### Features

- Add `--quiet` and `--verbose` options for the `pipx` subcommands
- Add ability to install multiple packages at once
- Delete directories directly instead of spawning rmdir on Windows

### Improved Documentation

- Add Scoop installation instructions

### Bugfixes

- "Failed to delete" error when using Microsoft Store Python
- "No pyvenv.cfg file" error when using Microsoft Store Python (#1164)

## [1.3.3](https://github.com/pypa/pipx/tree/1.3.3)

### Improved Documentation

- Make the logo more visible in dark mode

## [1.3.2](https://github.com/pypa/pipx/tree/1.3.2)

### Features

- The project version number is now dynamic and generated from the VCS at build time

### Improved Documentation

- Add additional example for --pip-args option, to docs/examples.md

## [1.3.1](https://github.com/pypa/pipx/tree/1.3.1)

### Bugfixes

- Fix combining of --editable and --force flag

## [1.3.0](https://github.com/pypa/pipx/tree/1.3.0)

### Features

- Allow running `pip` with `pipx run`
- Add `--with-suffix` for `pipx inject` command
- `pipx install`: emit a warning when `--force` and `--python` were passed at the same time
- Add explicit 3.12 support
- Make usage message in `pipx run` show `package_or_url`, so extra will be printed out as well
- Use the py launcher, if available, to select Python version with the `--python` option
- add pre-commit hook support
- Add `pipx install --preinstall` to support preinstalling build requirements
- Return an error message when directory can't be added to PATH successfully
- Expose manual pages included in an application installed with `pipx install`
- Check whether pip module exists in shared lib before performing any actions, such as `reinstall-all`.
- Drop `setuptools` and `wheel` from the shared libraries. This results in less time consumption when the libraries are
  automatically upgraded.
- Support [inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/)
  in `pipx run`.
- Imply `--include-apps` when running `pipx inject --include-deps`
- Add `--force-reinstall` to pip arguments when `--force` was passed
- Support including requirements in scripts run using `pipx run` (#916)
- Pass `pip_args` to `shared_libs.upgrade()`
- Fallback to user's log path if the default log path (`$PIPX_HOME/logs`) is not writable to aid with pipx being used
  for multi-user (e.g. system-wide) installs of applications
- Don't show escaped backslashes for paths in console output
- Move `pipx` paths to ensure compatibility with the platform-specific user directories
- Pass `--no-input` to pip when output is not piped to parent stdout
- Print all environment variables in `pipx environment`

### Improved Documentation

- Add more examples for `pipx run`
- Add subsection to make README easier to read

### Deprecations and Removals

- Drop support for Python 3.7

### Bugfixes

- Fix wrong interpreter usage when injecting local pip-installable dependencies into venvs
- Fix program name in generated manual page

## [1.2.1](https://github.com/pypa/pipx/tree/1.2.1)

### Bugfixes

- Fix compatibility to packaging 23.2+ by removing reliance on packaging's requirement validation logic and detecting a
  URL-based requirement in pipx. (#1070)

## [1.2.0](https://github.com/pypa/pipx/tree/1.2.0)

### Features

- Add `pipx uninject` command (#820)
- Ship a [zipapp](https://docs.python.org/3/library/zipapp.html) of pipx
- Match pip's behaviour when package name ends with archive extension (treat it as a path)
- Change the program name to `path/to/python -m pipx` when running as `python -m pipx`
- Improve the detection logic for MSYS2 to avoid entering infinite loop (#908) (#938)
- Remove extra trailing quote from exception message
- Fix EncodingWarning in `pipx_metadata_file`.

### Improved Documentation

- Add an example for installation from source with extras
- Fix `pipx run` examples and update Python versions used by `pipx install` examples

### Bugfixes

- Add test for pip module in `pipx reinstall` to fix an issue with `pipx reinstall-all` (#935)

## [1.1.0](https://github.com/pypa/pipx/tree/1.1.0)

### Features

- Add `pipx environment` command (#793)
- Add `list --short` option to list only package names (#804)
- Improve the behaviour of `shlex.split` on Windows, so paths on Windows can be handled properly when they are passed in
  `--pip-args`. (#794)
- [dev] Change github action job names
- Add additional examples for installation from git repos
- [packaging] Switch to [PEP 621](https://www.python.org/dev/peps/pep-0621/)
- Add a CACHEDIR.TAG to the cache directory to prevent it from being included in archives and backups. For more
  information about cache directory tags, see https://bford.info/cachedir

### Bugfixes

- Fix encoding issue on Windows when pip fails to install a package

### Improved Documentation

- Add more examples
- Fix the command for
  [installing development version](https://pipx.pypa.io/stable/installation/#install-pipx-development-versions). (#801)
- Fix test status badge in readme file

## [1.0.0](https://github.com/pypa/pipx/tree/1.0.0)

### Features

- Support [argcomplete 2.0.0](https://pypi.org/project/argcomplete/2.0.0) (#790)
- Include machinery to build a manpage for pipx with [argparse-manpage](https://pypi.org/project/argparse-manpage/).
- Add better handling for 'app not found' when a single app is present in the project, and an improved error message
  (#733)

### Bugfixes

- Fixed animations sending output to stdout, which can break JSON output. (#769)
- Fix typo in `pipx upgrade-all` output

## [0.17.0](https://github.com/pypa/pipx/tree/0.17.0)

- Support `pipx run` with version constraints and extras. (#697)

## [0.16.5](https://github.com/pypa/pipx/tree/0.16.5)

- Fixed `pipx list` output phrasing to convey that python version displayed is the one with which package was installed.
- Fixed `pipx install` to provide return code 0 if venv already exists, similar to pip’s behavior. (#736)
- [docs] Update ansible's install command in
  [Programs to Try document](https://pipx.pypa.io/stable/programs-to-try/#ansible) to work with Ansible 2.10+ (#742)

## [0.16.4](https://github.com/pypa/pipx/tree/0.16.4)

- Fix to `pipx ensurepath` to fix behavior in user locales other than UTF-8, to fix #644. The internal change is to use
  userpath v1.6.0 or greater. (#700)
- Fix virtual environment inspection for Python releases that uses an int for its release serial number. (#706)
- Fix PermissionError in windows when pipx manages itself. (#718)

## [0.16.3](https://github.com/pypa/pipx/tree/0.16.3)

- Organization: pipx is extremely pleased to now be a project of the Python Packaging Authority (PyPA)! Note that our
  github URL has changed to [pypa/pipx](https://github.com/pypa/pipx)
- Fixed `pipx list --json` to return valid json with no venvs installed. Previously would return an empty string to
  stdout. (#681)
- Changed `pipx ensurepath` bash behavior so that only one of {`~/.profile`, `~/.bash\_profile`} is modified with the
  extra pipx paths, not both. Previously, if a `.bash_profile` file was created where one didn't exist, it could cause
  problems, e.g. #456. The internal change is to use userpath v1.5.0 or greater. (#684)
- Changed default nox tests, Github Workflow tests, and pytest behavior to use local pypi server with fixed lists of
  available packages. This allows greater test isolation (no network pypi access needed) and determinism (fixed
  available dependencies.) It also allows running the tests offline with some extra preparation beforehand (See
  [Running Unit Tests Offline](https://pipx.pypa.io/stable/contributing/#running-unit-tests-offline)). The old style
  tests that use the internet to access pypi.org are still available using `nox -s tests_internet` or
  `pytest --net-pypiserver tests`. (#686)

* Colorama is now only installed on Windows. (#691)

## [0.16.2.1](https://github.com/pypa/pipx/tree/0.16.2.1)

- Changed non-venv-info warnings and notices from `pipx list` to print to stderr. This especially prevents
  `pipx list --json` from printing invalid json to stdout. (#680)
- Fixed bug that could cause uninstall on Windows with injected packages to uninstall too many apps from the local
  binary directory. (#679)

## [0.16.2.0](https://github.com/pypa/pipx/tree/0.16.2.0)

- Fixed bug #670 where uninstalling a venv could erroneously uninstall other apps from the local binary directory.
  (#672)
- Added `--json` switch to `pipx list` to output rich json-metadata for all venvs.
- Ensured log files are utf-8 encoded to prevent Unicode encoding errors from occurring with emojis. (#646)
- Fixed issue which made pipx incorrectly list apps as part of a venv when they were not installed by pipx. (#650)
- Fixed old regression that would prevent pipx uninstall from cleaning up linked binaries if the venv was old and did
  not have pipx metadata. (#651)
- Fixed bugs with suffixed-venvs on Windows. Now properly summarizes install, and actually uninstalls associated
  binaries for suffixed-venvs. (#653)
- Changed venv minimum python version to 3.6, removing python 3.5 which is End of Life. (#666)

## [0.16.1.0](https://github.com/pypa/pipx/tree/0.16.1.0)

- Introduce the `pipx.run` entry point group as an alternative way to declare an application for `pipx run`.
- Fix cursor show/hide to work with older versions of Windows. (#610)
- Support text colors on Windows. (#612)
- Better platform unicode detection to avoid errors and allow showing emojis when possible. (#614)
- Don't emit show cursor or hide cursor codes if STDERR is not a tty. (#620)
- Sped up `pipx list` (#624).
- pip errors no longer stream to the shell when pip fails during a pipx install. pip's output is now saved to a log
  file. In the shell, pipx will tell you the location of the log file and attempt to summarize why pip failed. (#625)
- For `reinstall-all`, fixed bug where missing python executable would cause error. (#634)
- Fix regression which prevented pipx from working with pythonloc (and `__pypackages__` folder). (#636)

## [0.16.0.0](https://github.com/pypa/pipx/tree/0.16.0.0)

- New venv inspection! The code that pipx uses to examine and determine metadata in an installed venv has been made
  faster, better, and more reliable. It now uses modern python libraries like `packaging` and `importlib.metadata` to
  examine installed venvs. It also now properly handles installed package extras. In addition, some problems pipx has
  had with certain characters (like periods) in package names should be remedied.
- Added reinstall command for reinstalling a single venv.
- Changed `pipx run` on non-Windows systems to actually replace pipx process with the app process instead of running it
  as a subprocess. (Now using python's `os.exec*`)
- [bugfix] Fixed bug with reinstall-all command when package have been installed using a specifier. Now the initial
  specifier is used.
- [bugfix] Override display of `PIPX_DEFAULT_PYTHON` value when generating web documentation for `pipx install` #523
- [bugfix] Wrap help documentation for environment variables.
- [bugfix] Fixed uninstall crash that could happen on Windows for certain packages
- [feature] Venv package name arguments now do not have to match exactly as pipx has them stored, but can be specified
  in any python-package-name-equivalent way. (i.e. case does not matter, and `.`, `-`, `_` characters are
  interchangeable.)
- [change] Venvs with a suffix: A suffix can contain any characters, but for purposes of uniqueness, python package name
  rules apply--upper- and lower-case letters are equivalent, and any number of `.`, `-`, or `_` characters in a row are
  equivalent. (e.g. if you have a suffixed venv `pylint_1.0A` you could not add another suffixed venv called
  `pylint--1-0a`, as it would not be a unique name.)
- [implementation detail] Pipx shared libraries (providing pip, setuptools, wheel to pipx) are no longer installed using
  pip arguments taken from the last regular pipx install. If you need to apply pip arguments to pipx's use of pip for
  its internal shared libraries, use PIP\_\* environment variables.
- [feature] Autocomplete for venv names is no longer restricted to an exact match to the literal venv name, but will
  autocomplete any logically-similar python package name (i.e. case does not matter, and `.`, `-`, `_` characters are
  all equivalent.)
- pipx now reinstall its internal shared libraries when the user executes `reinstall-all`.
- Made sure shell exit codes from every pipx command are correct. In the past some (like from `pipx upgrade`) were
  wrong. The exit code from `pipx runpip` is now the exit code from the `pip` command run. The exit code from
  `pipx list` will be 1 if one or more venvs have problems that need to be addressed.
- pipx now writes a log file for each pipx command executed to `$PIPX_HOME/logs`, typically `~/.local/pipx/logs`. pipx
  keeps the most recent 10 logs and deletes others.
- `pipx upgrade` and `pipx upgrade-all` now have a `--upgrade-injected` option which directs pipx to also upgrade
  injected packages.
- `pipx list` now detects, identifies, and suggests a remedy for venvs with old-internal data (internal venv names) that
  need to be updated.
- Added a "Troubleshooting" page to the pipx web documentation for common problems pipx users may encounter.
- pipx error, warning, and other messages now word-wrap so words are not split across lines. Their appearance is also
  now more consistent.

## [0.15.6.0](https://github.com/pypa/pipx/tree/0.15.6.0)

- [docs] Update license
- [docs] Display a more idiomatic command for registering completions on fish.
- [bugfix] Fixed regression in list, inject, upgrade, reinstall-all commands when suffixed packages are used.
- [bugfix] Do not reset package url during upgrade when main package is `pipx`
- Updated help text to show description for `ensurepath` and `completions` help
- Added support for user-defined default python interpreter via new `PIPX_DEFAULT_PYTHON`. Helpful for use with pyenv
  among other uses.
- [bugfix] Fixed bug where extras were ignored with a PEP 508 package specification with a URL.

## [0.15.5.1](https://github.com/pypa/pipx/tree/0.15.5.1)

- [bugfix] Fixed regression of 0.15.5.0 which erroneously made installing from a local path with package extras not
  possible.

## [0.15.5.0](https://github.com/pypa/pipx/tree/0.15.5.0)

- pipx now parses package specification before install. It removes (with warning) the `--editable` install option for
  any package specification that is not a local path. It also removes (with warning) any environment markers.
- Disabled animation when we cannot determine terminal size or if the number of columns is too small. (Fixes #444)
- [feature] Version of each injected package is now listed after name for `pipx list --include-injected`
- Change metadata recorded from version-specified install to allow upgrades in future. Adds pipx dependency on
  `packaging` package.
- [bugfix] Prevent python error in case where package has no pipx metadata and advise user how to fix.
- [feature] `ensurepath` now also ensures that pip user binary path containing pipx itself is in user's PATH if pipx was
  installed using `pip install --user`.
- [bugfix] For `pipx install`, fixed failure to install if user has `PIP_USER=1` or `user=true` in pip.conf. (#110)
- [bugfix] Requiring userpath v1.4.1 or later so ensure Windows bug is fixed for `ensurepath` (#437)
- [feature] log pipx version (#423)
- [feature] `--suffix` option for `install` to allow multiple versions of same tool to be installed (#445)
- [feature] pipx can now be used with the Windows embeddable Python distribution

## [0.15.4.0](https://github.com/pypa/pipx/tree/0.15.4.0)

- [feature] `list` now has a new option `--include-injected` to show the injected packages in the main apps
- [bugfix] Fixed bug that can cause crash when installing an app

## [0.15.3.1](https://github.com/pypa/pipx/tree/0.15.3.1)

- [bugfix] Workaround multiprocessing issues on certain platforms (#229)

## [0.15.3.0](https://github.com/pypa/pipx/tree/0.15.3.0)

- [feature] Use symlinks on Windows when symlinks are available

## [0.15.2.0](https://github.com/pypa/pipx/tree/0.15.2.0)

- [bugfix] Improved error reporting during venv metadata inspection.
- [bugfix] Fixed incompatibility with pypy as venv interpreter (#372).
- [bugfix] Replaced implicit dependency on setuptools with an explicit dependency on packaging (#339).
- [bugfix] Continue reinstalling packages after failure
- [bugfix] Hide cursor while pipx runs
- [feature] Add environment variable `USE_EMOJI` to allow enabling/disabling emojis (#376)
- [refactor] Moved all commands to separate files within the commands module (#255).
- [bugfix] Ignore system shared libraries when installing shared libraries pip, wheel, and setuptools. This also fixes
  an incompatibility with Debian/Ubuntu's version of pip (#386).

## [0.15.1.3](https://github.com/pypa/pipx/tree/0.15.1.3)

- [bugfix] On Windows, pipx now lists correct Windows apps (#217)
- [bugfix] Fixed a `pipx install` bug causing incorrect python binary to be used when using the optional --python
  argument in certain situations, such as running pipx from a Framework python on macOS and specifying a non-Framework
  python.

## [0.15.1.2](https://github.com/pypa/pipx/tree/0.15.1.2)

- [bugfix] Fix recursive search of dependencies' apps so no apps are missed.
- `upgrade-all` now skips editable packages, because pip disallows upgrading editable packages.

## [0.15.1.1](https://github.com/pypa/pipx/tree/0.15.1.1)

- [bugfix] fix regression that caused installing with --editable flag to fail package name determination.

## [0.15.1.0](https://github.com/pypa/pipx/tree/0.15.1.0)

- Add Python 3.8 to PyPI classifier and travis test matrix
- [feature] auto-upgrade shared libraries, including pip, if older than one month. Hide all pip warnings that a new
  version is available. (#264)
- [bugfix] pass pip arguments to pip when determining package name (#320)

## [0.15.0.0](https://github.com/pypa/pipx/tree/0.15.0.0)

Upgrade instructions: When upgrading to 0.15.0.0 or above from a pre-0.15.0.0 version, you must re-install all packages
to take advantage of the new persistent pipx metadata files introduced in this release. These metadata files store pip
specification values, injected packages, any custom pip arguments, and more in each main package's venv. You can do this
by running `pipx reinstall-all` or `pipx uninstall-all`, then reinstalling manually.

- `install` now has no `--spec` option. You may specify any valid pip specification for `install`'s main argument.
- `inject` will now accept pip specifications for dependency arguments
- Metadata is now stored for each application installed, including install options like `--spec`, and injected packages.
  This information allows upgrade, upgrade-all and reinstall-all to work properly even with non-pypi installed packages.
  (#222)
- `upgrade` options `--spec` and `--include-deps` were removed. Pipx now uses the original options used to install each
  application instead. (#222)
- `upgrade-all` options `--include-deps`, `--system-site-packages`, `--index-url`, `--editable`, and `--pip-args` were
  removed. Pipx now uses the original options used to install each application instead. (#222)
- `reinstall-all` options `--include-deps`, `--system-site-packages`, `--index-url`, `--editable`, and `--pip-args` were
  removed. Pipx now uses the original options used to install each application instead. (#222)
- Handle missing interpreters more gracefully (#146)
- Change `reinstall-all` to use system python by default for apps. Now use `--python` option to specify a different
  python version.
- Remove the PYTHONPATH environment variable when executing any command to prevent conflicts between pipx dependencies
  and package dependencies when pipx is installed via homebrew. Homebrew can use PYTHONPATH manipulation instead of
  virtual environments. (#233)
- Add printed summary after successful call to `pipx inject`
- Support associating apps with Python 3.5
- Improvements to animation status text
- Make `--python` argument in `reinstall-all` command optional
- Use threads on OS's without support for semaphores
- Stricter parsing when passing `--` argument as delimiter

## [0.14.0.0](https://github.com/pypa/pipx/tree/0.14.0.0)

- Speed up operations by using shared venv for `pip`, `setuptools`, and `wheel`. You can see more detail in the 'how
  pipx works' section of the documentation. (#164, @pfmoore)
- Breaking change: for the `inject` command, change `--include-binaries` to `--include-apps`
- Change all terminology from `binary` to `app` or `application`
- Improve argument parsing for `pipx run` and `pipx runpip`
- If `--force` is passed, remove existing files in PIPX_BIN_DIR
- Move animation to start of line, hide cursor when animating

## [0.13.2.3](https://github.com/pypa/pipx/tree/0.13.2.3)

- Fix regression when installing a package that doesn't have any entry points

## [0.13.2.2](https://github.com/pypa/pipx/tree/0.13.2.2)

- Remove unnecessary and sometimes incorrect check after `pipx inject` (#195)
- Make status text/animation reliably disappear before continuing
- Update animation symbols

## [0.13.2.1](https://github.com/pypa/pipx/tree/0.13.2.1)

- Remove virtual environment if installation did not complete. For example, if it was interrupted by ctrl+c or if an
  exception occurred for any reason. (#193)

## [0.13.2.0](https://github.com/pypa/pipx/tree/0.13.2.0)

- Add shell autocompletion. Also add `pipx completions` command to print instructions on how to add pipx completions to
  your shell.
- Un-deprecate `ensurepath`. Use `userpath` internally instead of instructing users to run the `userpath` cli command.
- Improve detection of PIPX_BIN_DIR not being on PATH
- Improve error message when an existing symlink exists in PIPX_BIN_DIR and points to the wrong location
- Improve handling of unexpected files in PIPX_HOME (@uranusjr)
- swap out of order logic in order to correctly recommend --include-deps (@joshuarli)
- [dev] Migrate from tox to nox

## [0.13.1.1](https://github.com/pypa/pipx/tree/0.13.1.1)

- Do not raise bare exception if no binaries found (#150)
- Update pipsi migration script

## [0.13.1.0](https://github.com/pypa/pipx/tree/0.13.1.0)

- Deprecate `ensurepath` command. Use `userpath append ~/.local/bin`
- Support redirects and proxies when downloading python files (i.e. `pipx run http://url/file.py`)
- Use tox for document generation and CI testing (CI tests are now functional rather than static tests on style and
  formatting!)
- Use mkdocs for documentation
- Change default cache duration for `pipx run` from 2 to 14 days

## [0.13.0.1](https://github.com/pypa/pipx/tree/0.13.0.1)

- Fix upgrade-all and reinstall-all regression

## [0.13.0.0](https://github.com/pypa/pipx/tree/0.13.0.0)

- Add `runpip` command to run arbitrary pip commands in pipx-managed virtual environments
- Do not raise error when running `pipx install PACKAGE` and the package has already been installed by pipx (#125). This
  is the cause of the major version change from 0.12 to 0.13.
- Add `--skip` argument to `upgrade-all` and `reinstall-all` commands, to let the user skip particular packages

## [0.12.3.3](https://github.com/pypa/pipx/tree/0.12.3.3)

- Update logic in determining a package's binaries during installation. This removes spurious binaries from the
  installation. (#104)
- Improve compatibility with Debian distributions by using `shutil.which` instead of `distutils.spawn.find_executable`
  (#102)

## [0.12.3.2](https://github.com/pypa/pipx/tree/0.12.3.2)

- Fix infinite recursion error when installing package such as `cloudtoken==0.1.84` (#103)
- Fix windows type errors (#96, #98)

## [0.12.3.1](https://github.com/pypa/pipx/tree/0.12.3.1)

- Fix "WindowsPath is not iterable" bug

## [0.12.3.0](https://github.com/pypa/pipx/tree/0.12.3.0)

- Add `--include-deps` argument to include binaries of dependent packages when installing with pipx. This improves
  compatibility with packages that depend on other installed packages, such as `jupyter`.
- Speed up `pipx list` output (by running multiple processes in parallel) and by collecting all metadata in a single
  subprocess call
- More aggressive cache directory removal when `--no-cache` is passed to `pipx run`
- [dev] Move inline text passed to subprocess calls to their own files to enable autoformatting, linting, unit testing

## [0.12.2.0](https://github.com/pypa/pipx/tree/0.12.2.0)

- Add support for PEP 582's `__pypackages__` (experimental). `pipx run BINARY` will first search in `__pypackages__` for
  binary, then fallback to installing from PyPI. `pipx run --pypackages BINARY` will raise an error if the binary is not
  found in `__pypackages__`.
- Fix regression when installing with `--editable` flag (#93)
- [dev] improve unit tests

## [0.12.1.0](https://github.com/pypa/pipx/tree/0.12.1.0)

- Cache and reuse temporary Virtual Environments created with `pipx run` (#61)
- Update binary discovery logic to find "scripts" like awscli (#91)
- Forward `--pip-args` to the pip upgrade command (previously the args were forwarded to install/upgrade commands for
  packages) (#77)
- When using environment variable PIPX_HOME, Virtual Environments will now be created at `$PIPX_HOME/venvs` rather than
  at `$PIPX_HOME`.
- [dev] refactor into multiple files, add more unit tests

## [0.12.0.4](https://github.com/pypa/pipx/tree/0.12.0.4)

- Fix parsing bug in pipx run

## [0.12.0.3](https://github.com/pypa/pipx/tree/0.12.0.3)

- list python2 as supported language so that pip installs with python2 will no longer install the pipx on PyPI from the
  original pipx owner. Running pipx with python2 will fail, but at least it will not be as confusing as running the pipx
  package from the original owner.

## [0.12.0.2](https://github.com/pypa/pipx/tree/0.12.0.2)

- forward arguments to run command correctly #90

## [0.12.0.1](https://github.com/pypa/pipx/tree/0.12.0.1)

- stop using unverified context #89

## [0.12.0.0](https://github.com/pypa/pipx/tree/0.12.0.0)

- Change installation instructions to use `pipx` PyPI name
- Add `ensurepath` command

## [0.11.0.2](https://github.com/pypa/pipx/tree/0.11.0.2)

- add version argument parsing back in (fixes regression)

## [0.11.0.1](https://github.com/pypa/pipx/tree/0.11.0.1)

- add version check, command check, fix printed version update installation instructions

## [0.11.0.0](https://github.com/pypa/pipx/tree/0.11.0.0)

- Replace `pipx BINARY` with `pipx run BINARY` to run a binary in an ephemeral environment. This is a breaking API
  change so the major version has been incremented. (Issue #69)
- upgrade pip when upgrading packages (Issue #72)
- support --system-site-packages flag (Issue #64)

## [0.10.4.1](https://github.com/pypa/pipx/tree/0.10.4.1)

- Fix version printed when `pipx --version` is run

## [0.10.4.0](https://github.com/pypa/pipx/tree/0.10.4.0)

- Add --index-url, --editable, and --pip-args flags
- Updated README with pipsi migration instructions

## [0.10.3.0](https://github.com/pypa/pipx/tree/0.10.3.0)

- Display python version in list
- Do not reinstall package if already installed (added `--force` flag to override)
- When upgrading all packages, print message only when package is updated
- Avoid accidental execution of pipx.**main**


================================================
FILE: docs/contributing.md
================================================
---
hide:
  - navigation
---

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](https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md).

## Submitting changes

1. Fork [the GitHub repository](https://github.com/pypa/pipx).
1. Make a branch off of `main` and commit your changes to it.
1. Add a changelog entry.
1. Submit a pull request to the `main` branch on GitHub, referencing an open issue.

### Changelog entries

The `CHANGELOG.md` file is built by [towncrier](https://pypi.org/project/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<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](https://github.com/astral-sh/python-build-standalone/releases) 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](#creating-a-developer-environment), or perform an editable
install:

```
python -m pip install -e .
python -m pipx --version
```

## Running Tests

### Setup

pipx uses [tox](https://pypi.org/project/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 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

`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.


================================================
FILE: docs/explanation/comparisons.md
================================================
## pipx vs pip

- pip is a general Python package installer. It can be used to install libraries or CLI applications with entrypoints.
- pipx is a specialized package installer. It can only be used to install packages with CLI entrypoints.
- pipx and pip both install packages from PyPI (or locally)
- pipx relies on pip (and venv)
- pipx replaces a subset of pip's functionality; it lets you install CLI applications but NOT libraries that you import
    in your code.
- you can install pipx with pip

Example interaction: Install pipx with pip: `pip install --user pipx`

## pipx vs poetry and pipenv

- pipx is used solely for application consumption: you install CLI apps with it
- pipenv and poetry are CLI apps used to develop applications and libraries
- all three tools wrap pip and virtual environments for more convenient workflows

Example interaction: Install pipenv and poetry with pipx: `pipx install poetry` Run pipenv or poetry with pipx:
`pipx run poetry --help`

## pipx vs venv

- venv is part of Python's standard library in Python 3.2 and above
- venv creates "virtual environments" which are sandboxed python installations
- pipx heavily relies on the venv package

Example interaction: pipx installs packages to environments created with venv. `pipx install black --verbose`

## pipx vs pyenv

- pyenv manages python versions on your system. It helps you install versions like Python 3.6, 3.7, etc.
- pipx installs packages in virtual environments and exposes their entrypoints on your PATH

Example interaction: Install a Python interpreter with pyenv, then install a package using pipx and that new
interpreter: `pipx install black --python=python3.11` where python3.11 was installed on the system with pyenv

## pipx vs pipsi

- pipx and pipsi both install packages in a similar way
- pipx is under active development. pipsi is no longer maintained.
- pipx always makes sure you're using the latest version of pip
- pipx has the ability to run an app in one line, leaving your system unchanged after it finishes (`pipx run APP`) where
    pipsi does not
- pipx has the ability to recursively install binaries from dependent packages
- pipx adds more useful information to its output
- pipx has more CLI options such as upgrade-all, reinstall-all, uninstall-all
- pipx is more modern. It uses Python 3.6+, and the `venv` package in the Python3 standard library instead of the python
    2 package `virtualenv`.
- pipx works with Python homebrew installations while pipsi does not (at least on my machine)
- pipx defaults to less verbose output
- pipx allows you to see each command it runs by passing the --verbose flag
- pipx prints emojis 😀

Example interaction: None. Either one or the other should be used. These tools compete for a similar workflow.

### Migrating to pipx from pipsi

After you have installed pipx, run
[migrate_pipsi_to_pipx.py](https://raw.githubusercontent.com/pypa/pipx/main/scripts/migrate_pipsi_to_pipx.py). Why not
do this with your new pipx installation?

```
pipx run https://raw.githubusercontent.com/pypa/pipx/main/scripts/migrate_pipsi_to_pipx.py
```

## pipx vs brew

- Both brew and pipx install cli tools
- They install them from different sources. brew uses a curated repository specifically for brew, and pipx generally
    uses PyPI.

Example interaction: brew can be used to install pipx, but they generally don't interact much.

## pipx vs npx

- Both can run cli tools (npx will search for them in node_modules, and if not found run in a temporary environment.
    `pipx run` will search in `__pypackages__` and if not found run in a temporary environment)
- npx works with JavaScript and pipx works with Python
- Both tools attempt to make running executables written in a dynamic language (JS/Python) as easy as possible
- pipx can also install tools globally; npx cannot

Example interaction: None. These tools work for different languages.

## pipx vs pip-run

[pip-run](https://github.com/jaraco/pip-run) is focused on running **arbitrary Python code in ephemeral environments**
while pipx is focused on running **Python binaries in ephemeral and non-ephemeral environments**.

For example these two commands both install poetry to an ephemeral environment and invoke poetry with `--help`.

```
pipx run poetry --help
pip-run poetry -- -m poetry --help
```

Example interaction: None.

## pipx vs fades

[fades](https://github.com/PyAr/fades) is a tool to run **individual** Python scripts inside automatically provisioned
virtualenvs with their dependencies installed.

- Both [fades](https://github.com/PyAr/fades#how-to-mark-the-dependencies-to-be-installed) and
    [pipx run](../reference/examples.md#pipx-run-examples) allow specifying a script's dependencies in specially
    formatted comments, but the exact syntax differs. (pipx's syntax is standardized by a
    [provisional specification](https://packaging.python.org/en/latest/specifications/inline-script-metadata/), fades's
    syntax is not standardized.)
- Both tools automatically set up reusable virtualenvs containing the necessary dependencies.
- Both can download Python scripts/packages to execute from remote resources.
- fades can only run individual script files while pipx can also run packages.

Example interaction: None.

## pipx vs pae/pactivate

_pae_ is a Bash command-line function distributed with [pactivate](https://github.com/cynic-net/pactivate) that uses
pactivate to create non-ephemeral environments focused on general use, rather than just running command-line
applications.

There is [a very detailed comparison here](https://github.com/cynic-net/pactivate/blob/main/doc/vs-pipx.md), but to
briefly summarize:

Similarities:

- Both create isolated environments without having to specify (and remember) a directory in which to store them.
- Both allow you to use any Python interpreter available on your system (subject to version restrictions below).

pae advantages:

- Supports all versions of Python from 2.7 upward. pipx requires ≥3.9.
- Fewer dependencies. (See the detailed comparison for more information.)
- Easier to have multiple versions of a single program and/or use different Python versions for a single program.
- Somewhat more convenient for running arbitrary command-line programs in virtual environments, installing multiple
    packages in a single environment, and activating virtual environments.
- Integrates well with source code repos using [pactivate](https://github.com/cynic-net/pactivate).

pae disadvantages:

- Usable with Bash shell only.
- Slightly less quick and convenient for installing/running command-line programs from single Python packages.
- Can be slower than pipx at creating virtual environments.

Example interaction: None. Either one or the other should be used. These tools compete for a similar workflow.


================================================
FILE: docs/explanation/how-pipx-works.md
================================================
## How it Works

### `pipx install`

When installing a package and its binaries on Linux (`pipx install package`), pipx first creates or reuses a shared
virtual environment at `~/.local/share/pipx/shared/` that contains the packaging library `pip`, ensuring it is updated
to its latest version. It then creates an isolated virtual environment at `~/.local/share/pipx/venvs/PACKAGE` that uses
the shared pip via a [.pth file](https://docs.python.org/3/library/site.html) and installs the desired package into it.

Once the package is installed, pipx exposes its console scripts and GUI scripts by symlinking them into `~/.local/bin`
(for example, `~/.local/bin/black` -> `~/.local/share/pipx/venvs/black/bin/black`). It also symlinks any manual pages
into `~/.local/share/man/man[1-9]`. As long as `~/.local/bin/` is on your `PATH`, the new commands are available
globally, and on systems with `man` support the pages are accessible too.

Adding the `--global` flag to any `pipx` command executes the action in global scope, exposing the app to all system
users. See the [configuration reference](../how-to/configure-paths.md#-global-argument) for details. Note that this is
not available on Windows.

```mermaid
flowchart LR
    A["pipx install black"] --> B["shared venv<br/>(pip)"]
    B --> C["create venv<br/>~/.local/share/pipx/<br/>venvs/black/"]
    C --> D["pip install black"]
    D --> E["symlink binaries<br/>~/.local/bin/black"]
    D --> F["symlink man pages<br/>~/.local/share/man/"]

    style A fill:#3f72af,color:#fff
    style B fill:#2a9d8f,color:#fff
    style C fill:#2a9d8f,color:#fff
    style D fill:#2a9d8f,color:#fff
    style E fill:#388e3c,color:#fff
    style F fill:#388e3c,color:#fff
```

### `pipx run`

`pipx run BINARY` reuses the same shared pip environment, then either reuses a cached virtual environment or creates a
new temporary one. The cache key is a hash of the package name, spec, python version, and pip arguments. pipx creates a
virtual environment with `python -m venv`, installs the package, and invokes the binary.

Cached environments expire after a few days. On next run, pipx fetches the latest version.

```mermaid
flowchart LR
    A["pipx run pycowsay"] --> B["shared venv<br/>(pip)"]
    B --> C{"cached<br/>venv?"}
    C -- "yes" --> E["reuse cached venv"]
    C -- "no" --> D["create temp venv<br/>pip install pycowsay"]
    D --> F["invoke binary"]
    E --> F

    style A fill:#3f72af,color:#fff
    style B fill:#2a9d8f,color:#fff
    style C fill:#c78c20,color:#fff
    style D fill:#2a9d8f,color:#fff
    style E fill:#2a9d8f,color:#fff
    style F fill:#388e3c,color:#fff
```

### Directory layout

The overall directory structure that pipx manages looks like this:

```mermaid
flowchart TD
    HOME["~"] --> BIN["~/.local/bin/<br/>(on PATH)"]
    HOME --> DATA["~/.local/share/pipx/"]
    DATA --> SHARED["shared/<br/>(pip, setuptools)"]
    DATA --> VENVS["venvs/"]
    VENVS --> V1["black/"]
    VENVS --> V2["poetry/"]
    VENVS --> V3["ruff/"]
    V1 --> V1BIN["bin/black"]
    BIN --> |"symlink"| V1BIN

    style HOME fill:#3f72af,color:#fff
    style BIN fill:#388e3c,color:#fff
    style DATA fill:#2a9d8f,color:#fff
    style SHARED fill:#c78c20,color:#fff
    style VENVS fill:#2a9d8f,color:#fff
    style V1 fill:#7c4dff,color:#fff
    style V2 fill:#7c4dff,color:#fff
    style V3 fill:#7c4dff,color:#fff
    style V1BIN fill:#7c4dff,color:#fff
```

You can do all of this yourself. pipx automates it. Pass `--verbose` to see every command and argument pipx runs.


================================================
FILE: docs/explanation/index.md
================================================
# Explanation

Design rationale and background knowledge. Read these to understand *why* pipx works the way it does.

- [How pipx Works](how-pipx-works.md) — virtual environment architecture and the shared pip library.
- [Comparisons](comparisons.md) — how pipx relates to pip, poetry, pipenv, pyenv, brew, npx, and more.
- [Making Packages Compatible](making-packages-compatible.md) — adding entry points and man pages for pipx support.


================================================
FILE: docs/explanation/making-packages-compatible.md
================================================
## Developing for pipx

If you are a developer and want 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).

=== "pyproject.toml"

    ```ini
    [project.scripts]
    foo = "my_package.some_module:main_func"
    bar = "other_module:some_func"

    [project.gui-scripts]
    baz = "my_package_gui:start_func"
    ```

=== "setup.cfg"

    ```ini
    [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.py"

    ```python
    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',
            ]
        },
    )
    ```

In this case `foo` and `bar` (and `baz` on Windows) would be available as "applications" to pipx after installing the
above example package, invoking their corresponding entry point functions.

```mermaid
flowchart LR
    TOML["pyproject.toml<br/>[project.scripts]"] --> |"defines"| EP["entry points<br/>foo, bar"]
    EP --> |"pipx install"| VENV["isolated venv"]
    VENV --> |"symlinks"| BIN["~/.local/bin/<br/>foo, bar"]
    BIN --> |"invokes"| FUNC["my_package:<br/>main_func()"]

    style TOML fill:#3f72af,color:#fff
    style EP fill:#2a9d8f,color:#fff
    style VENV fill:#7c4dff,color:#fff
    style BIN fill:#388e3c,color:#fff
    style FUNC fill:#c78c20,color:#fff
```

### 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.

=== "pyproject.toml"

    ```toml
    [project.entry-points."pipx.run"]
    my-package = "my_package.cli:main"
    ```

=== "setup.cfg"

    ```ini
    [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](https://github.com/pypa/build) package uses this pattern so that `pipx run build` works even though
build's console script is named `pyproject-build`.

### Manual pages

If you wish to provide documentation via `man` pages on UNIX-like systems then these can be added as data files:

=== "setuptools"

    ```toml title="pyproject.toml"
    [tool.setuptools.data-files]
    "share/man/man1" = [
      "manpage.1",
    ]
    ```

    ```ini title="setup.cfg"
    [options.data_files]
    share/man/man1 =
        manpage.1
    ```

    ```python title="setup.py"
    setup(
        # other arguments here...
        data_files=[('share/man/man1', ['manpage.1'])]
    )
    ```

    > [!WARNING]
    > The `data-files` keyword is "discouraged" in the
    > [setuptools documentation](https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#setuptools-specific-configuration)
    > but there is no alternative if `man` pages are a requirement.

=== "pdm-backend"

    ```toml title="pyproject.toml"
    [tool.pdm.build]
    source-includes = ["share"]

    [tool.pdm.build.wheel-data]
    data = [
      { path = "share/man/man1/*", relative-to = "." },
    ]
    ```

In this case the manual page `manpage.1` could be accessed by the user after installing the above example package.

For a real-world example, see [pycowsay](https://github.com/cs01/pycowsay/blob/master/setup.py)'s `setup.py` source
code.

You can read more about entry points
[here](https://setuptools.pypa.io/en/latest/userguide/quickstart.html#entry-points-and-automatic-script-creation).

[^1]: This is often the `[project]` table, but might also be differently named. Read more in the
    [PyPUG](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#writing-your-pyproject-toml).


================================================
FILE: docs/how-to/configure-paths.md
================================================
## Installation Options

The default binary location for pipx-installed apps is `~/.local/bin`. This can be overridden with the environment
variable `PIPX_BIN_DIR`. The default manual page location for pipx-installed apps is `~/.local/share/man`. This can be
overridden with the environment variable `PIPX_MAN_DIR`. If the `--global` option is used, the default locations are
`/usr/local/bin` and `/usr/local/share/man` respectively and can be overridden with `PIPX_GLOBAL_BIN_DIR` and
`PIPX_GLOBAL_MAN_DIR`.

pipx's default virtual environment location is typically `~/.local/share/pipx` on Linux/Unix, `~/.local/pipx` on macOS
and `~\pipx` on Windows. For compatibility reasons, if `~/.local/pipx` on Linux, `%USERPROFILE%\AppData\Local\pipx` or
`~\.local\pipx` on Windows or `~/Library/Application Support/pipx` on macOS exists, it will be used as the default
location instead. This can be overridden with the `PIPX_HOME` environment variable. If the `--global` option is used,
the default location is always `/opt/pipx` and can be overridden with `PIPX_GLOBAL_HOME`.

In case one of these fallback locations exist, we recommend either manually moving the pipx files to the new default
location (see the [Moving your pipx installation](move-installation.md) section of the docs), or setting the `PIPX_HOME`
environment variable (discarding files existing in the fallback location).

As an example, you can install global apps accessible by all users on your system with either of the following commands
(on MacOS, Linux, and Windows WSL):

```
sudo PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin PIPX_MAN_DIR=/usr/local/share/man pipx install <PACKAGE>
# or shorter (with pipx>=1.5.0)
sudo pipx install --global <PACKAGE>
```

> [!NOTE]
> After version 1.2.0, the default pipx paths have been moved from `~/.local/pipx` to specific user data directories on
> each platform using [platformdirs](https://pypi.org/project/platformdirs/) library
>
> | Old Path               | New Path                                   |
> | ---------------------- | ------------------------------------------ |
> | `~/.local/pipx/.trash` | `platformdirs.user_data_dir()/pipx/trash`  |
> | `~/.local/pipx/shared` | `platformdirs.user_data_dir()/pipx/shared` |
> | `~/.local/pipx/venvs`  | `platformdirs.user_data_dir()/pipx/venvs`  |
> | `~/.local/pipx/.cache` | `platformdirs.user_cache_dir()/pipx`       |
> | `~/.local/pipx/logs`   | `platformdirs.user_log_dir()/pipx/log`     |
>
> `user_data_dir()`, `user_cache_dir()` and `user_log_dir()` resolve to appropriate platform-specific user data, cache
> and log directories. See the
> [platformdirs documentation](https://platformdirs.readthedocs.io/en/latest/api.html#platforms) for details.
>
> This was reverted in 1.5.0 for Windows and macOS. We heavily recommend not using these locations on Windows and macOS
> anymore, due to multiple incompatibilities discovered with these locations, documented
> [here](troubleshoot.md#why-are-spaces-in-the-pipx_home-path-bad).

### Customising your installation

#### `--global` argument

The `--global` flag installs applications into a system-wide location accessible to all users. It must be placed
**after** the subcommand, not before it:

```
# correct
sudo pipx install --global pycowsay
sudo pipx list --global

# wrong (--global is silently ignored when placed before the subcommand)
sudo pipx --global install pycowsay
```

Default global paths are `/usr/local/bin` for binaries, `/usr/local/share/man` for man pages, and `/opt/pipx` for
virtual environments. Override them with `PIPX_GLOBAL_BIN_DIR`, `PIPX_GLOBAL_MAN_DIR`, and `PIPX_GLOBAL_HOME`. Run
`sudo pipx ensurepath --global` to add the global binary directory to the system `PATH`.

The `--global` flag is not supported on Windows.

#### `--prepend` argument

The `--prepend` argument can be passed to the `pipx ensurepath` command to prepend the `pipx` bin directory to the
user's PATH environment variable instead of appending to it. This can be useful if you want to prioritise
`pipx`-installed binaries over system-installed binaries of the same name.

### Configuring pip for pipx

pipx uses pip internally for all package installs, including its own shared libraries (pip, setuptools). To point pip at
a private index or pass custom options, set `PIP_*` environment variables. pipx forwards them to every pip invocation.

For example, to use a private package index:

```
export PIP_INDEX_URL=https://my-private-index.example.com/simple/
export PIP_TRUSTED_HOST=my-private-index.example.com
pipx install my-private-package
```

These variables also apply when pipx upgrades its shared libraries (`pipx upgrade-shared`). You can set them permanently
in your shell profile or in pip's own config file (`pip.conf` / `pip.ini`). See the
[pip configuration documentation](https://pip.pypa.io/en/stable/topics/configuration/) for details.

Per-command pip options can be passed with `--pip-args`:

```
pipx install my-package --pip-args='--no-cache-dir --trusted-host=my-host'
```


================================================
FILE: docs/how-to/index.md
================================================
# How-to Guides

Recipes for common pipx tasks. Each guide covers a specific goal and assumes you have pipx installed.

- [Install pipx](install-pipx.md) — system requirements and OS-specific installation steps.
- [Upgrade pipx](upgrade-pipx.md) — keep pipx itself up to date.
- [Inject Packages](inject-packages.md) — add extra packages into an existing pipx environment.
- [Pin Packages](pin-packages.md) — hold a package at its current version.
- [Run Scripts](run-scripts.md) — run specific versions, from URLs, or from source control.
- [Use with pre-commit](use-with-pre-commit.md) — integrate pipx with pre-commit hooks.
- [Configure Paths](configure-paths.md) — customise `PIPX_HOME`, `PIPX_BIN_DIR`, and global installs.
- [Move Installation](move-installation.md) — relocate your pipx directory.
- [Shell Completions](shell-completions.md) — enable tab completion for your shell.
- [Troubleshoot](troubleshoot.md) — fix common problems.


================================================
FILE: docs/how-to/inject-packages.md
================================================
## Inject a package

`pipx inject` adds extra packages into an existing pipx-managed virtual environment. If you have `ipython` installed and
want `matplotlib` available inside it:

```
pipx inject ipython matplotlib
```

Inject multiple packages at once, from a requirements file, or both:

```
pipx inject ipython matplotlib pandas
pipx inject ipython -r useful-packages.txt
pipx inject ipython extra-pkg -r more-packages.txt
```

### Expose injected apps

By default, injected packages do not add their entry points to your `PATH`. Use `--include-apps` to expose them:

```
pipx inject ipython black --include-apps
```

`--include-deps` exposes entry points from the injected package's dependencies too (implies `--include-apps`).

### Other flags

- `--force` / `-f` reinstalls the package even if it is already injected.
- `--editable` / `-e` installs the package in editable (development) mode.
- `--with-suffix SUFFIX` targets a suffixed venv (e.g. `ipython_3.11`).
- `--pip-args` passes extra arguments to pip (e.g. `--pip-args='--no-cache-dir'`).
- `--index-url` / `-i` sets the PyPI index URL for this inject.
- `--system-site-packages` gives the venv access to the system site-packages.


================================================
FILE: docs/how-to/install-pipx.md
================================================
## System Requirements

python 3.9+ is required to install pipx. pipx can run binaries from packages with Python 3.3+. Don't have Python 3.9 or
later? See [Python 3 Installation & Setup Guide](https://realpython.com/installing-python/).

You also need to have `pip` installed on your machine for `python3`. The installation process varies from system to
system. Consult [pip's installation instructions](https://pip.pypa.io/en/stable/installing/). Installing on Linux works
best with a
[Linux Package Manager](https://packaging.python.org/guides/installing-using-linux-tools/#installing-pip-setuptools-wheel-with-linux-package-managers).

pipx works on macOS, Linux, and Windows.

[![Packaging status](https://repology.org/badge/vertical-allrepos/pipx.svg?columns=3&exclude_unsupported=1)](https://repology.org/metapackage/pipx/versions)

## Installing pipx

### On macOS:

```
brew install pipx
pipx ensurepath
```

#### Additional (optional) commands

To allow pipx actions in global scope (requires pipx 1.5.0+):

```
sudo pipx ensurepath --global
```

To prepend the pipx bin directory to PATH instead of appending it (requires pipx 1.7.0+):

```
sudo pipx ensurepath --prepend
```

> [!NOTE]
> Some distributions ship older pipx versions (e.g. Ubuntu 24.04 ships v1.4.3). If `--global` or `--prepend` fails with
> "unrecognized arguments", upgrade pipx first: `pip install --user --upgrade pipx`, or install a newer version from a
> different source.

For more details, refer to [Customising your installation](configure-paths.md).

### On Linux:

- Ubuntu 23.04 or above

```
sudo apt update
sudo apt install pipx
pipx ensurepath
```

- Fedora:

```
sudo dnf install pipx
pipx ensurepath
```

- Using `pip` on other distributions:

```
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

> [!NOTE]
> Distributions that adopt [PEP 668](https://peps.python.org/pep-0668/) (Ubuntu 23.04+, Debian 12+, Fedora 38+) mark the
> system Python as externally managed. Running `pip install --user` on these systems fails with an
> `externally-managed-environment` error. Use your distribution's package manager (`apt install pipx`,
> `dnf install pipx`) instead. If no distro package exists, install pipx inside its own virtual environment:
>
> ```
> python3 -m venv ~/.local/share/pipx-venv
> ~/.local/share/pipx-venv/bin/pip install pipx
> ln -s ~/.local/share/pipx-venv/bin/pipx ~/.local/bin/pipx
> pipx ensurepath
> ```

#### Additional (optional) commands

To allow pipx actions in global scope (requires pipx 1.5.0+):

```
sudo pipx ensurepath --global
```

To prepend the pipx bin directory to PATH instead of appending it (requires pipx 1.7.0+):

```
sudo pipx ensurepath --prepend
```

For more details, refer to [Customising your installation](configure-paths.md).

> [!NOTE]
> If you installed pipx with `pip install --user`, the `pipx` binary lives in your user directory (e.g.
> `~/.local/bin/pipx`). Running `sudo pipx` will fail because root's `PATH` does not include your user bin directory.
> Use the full path instead: `sudo ~/.local/bin/pipx ensurepath --global`. Alternatively, install pipx system-wide with
> `sudo pip install pipx` (without `--user`) or use your distribution's package manager.

### On Windows:

- Install via [Scoop](https://scoop.sh/):

```
scoop install pipx
pipx ensurepath
```

- Install via pip (requires pip 19.0 or later)

```
# If you installed python using Microsoft Store, replace `py` with `python3` in the next line.
py -m pip install --user pipx
```

It is possible (even most likely) the above finishes with a WARNING looking similar to this:

```
WARNING: The script pipx.exe is installed in `<USER folder>\AppData\Roaming\Python\Python3x\Scripts` which is not on PATH
```

If so, go to the mentioned folder, allowing you to run the pipx executable directly. Enter the following line (even if
you did not get the warning):

```
.\pipx.exe ensurepath
```

This will add both the above mentioned path and the `%USERPROFILE%\.local\bin` folder to your search path. Restart your
terminal session and verify `pipx` does run.

### On FreeBSD:

- Install via package manager

```sh
pkg install -y py311-pipx
```

- Install via pip

```sh
pip install --user pipx
pipx ensurepath
```

### Using pipx without installing (via zipapp)

The zipapp can be downloaded from [Github releases](https://github.com/pypa/pipx/releases) and you can invoke it with a
Python 3.9+ interpreter:

```
python pipx.pyz ensurepath
```

### Self-managed pipx

You can use pipx to manage its own installation. This keeps pipx up to date through `pipx upgrade pipx` and avoids
relying on distro packages that may ship older versions. Bootstrap it with a temporary virtual environment:

```
python3 -m venv /tmp/bootstrap
/tmp/bootstrap/bin/pip install pipx
/tmp/bootstrap/bin/pipx install pipx
rm -rf /tmp/bootstrap
pipx ensurepath
```

After this, `pipx upgrade pipx` upgrades pipx like any other pipx-managed application. On Windows, pipx cannot delete
its own running executable, so it moves locked files to a trash directory and cleans them up on the next run.

## Installing packages from source control

pipx accepts any source pip supports, including git repositories. Using `black` as an example:

```
pipx install git+https://github.com/psf/black.git
pipx install git+ssh://git@github.com/psf/black # using ssh
pipx install git+https://github.com/psf/black.git@branch  # branch of your choice
pipx install git+https://github.com/psf/black.git@ce14fa8b497bae2b50ec48b3bd7022573a59cdb1  # git hash
pipx install https://github.com/psf/black/archive/18.9b0.zip  # install a release
```

Use pip's `egg` syntax when installing extras:

```
pipx install "git+https://github.com/psf/black.git#egg=black[jupyter]"
```

### Installing from a pull request

To test a package from an open pull request, find the fork owner and branch name on the PR page, then build the git URL.
For example, PR #794 from user `contributor` on branch `fix-something`:

```
pipx install git+https://github.com/contributor/pipx.git@fix-something
```

If the PR branch has been merged, use the merge commit hash instead:

```
pipx install git+https://github.com/pypa/pipx.git@abc123def
```


================================================
FILE: docs/how-to/move-installation.md
================================================
## Moving your pipx installation

The below code snippets show how to move your pipx installation to a new directory. As an example, they move from a
non-default location to the current default locations. If you wish to move to a different location, just replace the
`NEW_LOCATION` value.

### MacOS

Current default location: `~/.local`

```bash
NEW_LOCATION=~/.local
cache_dir=$(pipx environment --value PIPX_VENV_CACHEDIR)
logs_dir=$(pipx environment --value PIPX_LOG_DIR)
trash_dir=$(pipx environment --value PIPX_TRASH_DIR)
home_dir=$(pipx environment --value PIPX_HOME)
rm -rf "$cache_dir" "$logs_dir" "$trash_dir"
mkdir -p $NEW_LOCATION && mv "$home_dir" $NEW_LOCATION
pipx reinstall-all
```

### Linux

Current default location: `~/.local/share`

```bash
cache_dir=$(pipx environment --value PIPX_VENV_CACHEDIR)
logs_dir=$(pipx environment --value PIPX_LOG_DIR)
trash_dir=$(pipx environment --value PIPX_TRASH_DIR)
home_dir=$(pipx environment --value PIPX_HOME)
# If you wish another location, replace the expression below
# and set `NEW_LOCATION` explicitly
NEW_LOCATION="${XDG_DATA_HOME:-$HOME/.local/share}"
rm -rf "$cache_dir" "$logs_dir" "$trash_dir"
mkdir -p $NEW_LOCATION && mv "$home_dir" $NEW_LOCATION
pipx reinstall-all
```

### Windows

Current default location: `~/pipx`

```powershell
$NEW_LOCATION = Join-Path "$HOME" 'pipx'
$cache_dir = pipx environment --value PIPX_VENV_CACHEDIR
$logs_dir = pipx environment --value PIPX_LOG_DIR
$trash_dir = pipx environment --value PIPX_TRASH_DIR
$home_dir = pipx environment --value PIPX_HOME

Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$cache_dir", "$logs_dir", "$trash_dir"

# Remove the destination directory to ensure rename behavior of `Move-Item`
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$NEW_LOCATION"

Move-Item -Path $home_dir -Destination "$NEW_LOCATION"
pipx reinstall-all
```

If you would prefer doing it in bash via git-bash/WSL, feel free to use the MacOS/Linux instructions, changing the
`$NEW_LOCATION` to the Windows version.


================================================
FILE: docs/how-to/pin-packages.md
================================================
## Pin installed packages

Use `pipx pin` when you need to hold an installation at its current version. Pinned packages are skipped by
`pipx upgrade`, `pipx upgrade-all`, and `pipx reinstall`, so the environment keeps its existing app and dependency
versions until you unpin it.

- `pipx pin PACKAGE` — pins the main package and any injected packages in that virtual environment.
- `pipx pin PACKAGE --injected-only` — leaves the main package upgradable but pins every injected package instead.
- `pipx pin PACKAGE --skip PKG_A PKG_B` — pins injected packages except the ones you list (the flag implies
    `--injected-only`).
- `pipx unpin PACKAGE` — re-enables upgrades for the package and anything that was pinned with it.
- `pipx list --pinned` — shows every pinned environment; add `--include-injected` to see pinned injected packages.

pipx tracks the main package and any injected packages. It does not record individual transitive dependencies, so there
is no way to pin a single dependency in isolation. Pinning the main package protects its dependency set because pipx
skips running `pip install --upgrade` for that environment.


================================================
FILE: docs/how-to/run-scripts.md
================================================
## Running a specific version of a package

The `PACKAGE` argument is a
[requirement specifier](https://packaging.python.org/en/latest/glossary/#term-Requirement-Specifier), so you can pin
versions, ranges, or extras:

```
pipx run mpremote==1.20.0
pipx run --spec esptool==4.6.2 esptool.py
pipx run --spec 'esptool>=4.5' esptool.py
```

Requirement specifiers containing `>`, `<`, or spaces need quoting.

## Running with extra dependencies

`--with` adds packages to the temporary environment alongside the main app:

```
pipx run --with requests --with rich my-script.py
```

## Running from source control

`pipx run` accepts git URLs through `--spec`. Using `black` as an example:

```
pipx run --spec git+https://github.com/psf/black.git black
pipx run --spec git+ssh://git@github.com/psf/black black
pipx run --spec git+https://github.com/psf/black.git@branch black
pipx run --spec git+https://github.com/psf/black.git@ce14fa8b497bae2b50ec48b3bd7022573a59cdb1 black
pipx run --spec https://github.com/psf/black/archive/18.9b0.zip black
```

## Running from URL

You can run `.py` files hosted anywhere:

```
pipx run https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py
```

## Running scripts with dependencies (PEP 723)

Scripts can declare their own dependencies using
[inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/). pipx reads the
`# /// script` block and installs the listed packages before running:

```python
# test.py

# /// script
# dependencies = ["requests"]
# ///

import sys
import requests
project = sys.argv[1]
data = requests.get(f"https://pypi.org/pypi/{project}/json").json()
print(data["info"]["version"])
```

```
> pipx run test.py pipx
1.9.0
```

pipx creates a cached virtual environment keyed to the script's dependency list. Changing the dependencies creates a
fresh environment.


================================================
FILE: docs/how-to/shell-completions.md
================================================
## Shell Completion

You can easily get your shell's tab completions working by following instructions printed with this command:

```
pipx completions
```


================================================
FILE: docs/how-to/troubleshoot.md
================================================
## Wrong package version installed

pipx creates venvs using your default Python interpreter. pip resolves the latest package version compatible with that
interpreter. If a package drops support for your Python version, pip installs an older release without warning.

Check which Python pipx uses with `pipx environment --value PIPX_DEFAULT_PYTHON`. To install with a different Python,
pass `--python`:

```
pipx install my-package --python python3.12
```

If the desired Python version is not installed, add `--fetch-missing-python` and pipx downloads a standalone build:

```
pipx install my-package --python 3.13 --fetch-missing-python
```

## `reinstall-all` fixes most issues

The following command should fix many problems you may encounter as a pipx user:

```
pipx reinstall-all
```

This is a good fix for the following problems:

- System python was upgraded and the python used with a pipx-installed package is no longer available
- pipx upgrade causes issues with old pipx-installed packages

pipx has been upgraded a lot over the years. If you are a long-standing pipx user (thanks, by the way!) then you may
have old pipx-installed packages that have internal data that is different than what pipx currently expects. By
executing `pipx reinstall-all`, pipx will re-write its internal data and this should fix many issues you may encounter.

**Note:** If your pipx-installed package was installed using a pipx version before 0.15.0.0 and you want to specify
particular options, then you may want to uninstall and install it manually:

```
pipx uninstall <mypackage>
pipx install <mypackage>
```

## Diagnosing problems using `list`

```
pipx list
```

will not only list all of your pipx-installed packages, but can also diagnose some problems with them, as well as
suggest solutions.

## Specifying pipx options

The most reliable method to specify command-line options that require an argument is to use an `=`-sign. An example:

```
pipx install pycowsay --pip-args="--no-cache-dir"
```

Another example for ignoring ssl/tls errors:

```
pipx install termpair --pip-args '--trusted-host files.pythonhosted.org --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host github.com'"
```

## Check for `PIP_*` environment variables

pipx uses `pip` to install and manage packages. If you see pipx exhibiting strange behavior on install or upgrade, check
that you don't have special environment variables that affect `pip`'s behavior in your environment.

To check for `pip` environment variables, execute the following depending on your system:

### Unix or macOS

```
env | grep '^PIP_'
```

### Windows PowerShell

```
ls env:PIP_*
```

### Windows `cmd`

```
set PIP_
```

Reference: [pip Environment Variables](https://pip.pypa.io/en/stable/user_guide/#environment-variables)

## `pipx` log files

pipx records a verbose log file for every `pipx` command invocation. The logs for the last 10 `pipx` commands can be
found in `$XDG_STATE_HOME/pipx/logs` or user's log path if the former is not writable by the user.

For most users this location is `~/.local/state/pipx/logs`, where `~` is your home directory.

## `sudo pipx` not found

If you installed pipx with `pip install --user`, the binary lives in your user directory (e.g. `~/.local/bin/pipx`).
Root's `PATH` does not include that directory, so `sudo pipx` fails with "command not found". Use the full path instead:

```
sudo ~/.local/bin/pipx ensurepath --global
```

To avoid this, install pipx through your distribution's package manager (`apt install pipx`, `dnf install pipx`) or
install it system-wide with `sudo pip install pipx` (without `--user`).

## Debian, Ubuntu issues

If you have issues using pipx on Debian, Ubuntu, or other Debian-based linux distributions, make sure you have the
following packages installed on your system. (Debian systems do not install these by default with their python
installations.)

```
sudo apt install python3-venv python3-pip
```

Reference:
[Python Packaging User Guide: Installing pip/setuptools/wheel with Linux Package Managers](https://packaging.python.org/guides/installing-using-linux-tools)

## macOS issues

If you want to use a pipx-installed package in a shebang (a common example is the AWS CLI), you will likely not be able
to, because the binary will be stored under `~/Library/Application Support/pipx/`. The space in the path is not
supported in a shebang. A simple solution is symlinking `~/Library/Application Support/pipx` to
`~/Library/ApplicationSupport/pipx`, and using that as the path in the shebang instead.

```
mkdir $HOME/Library/ApplicationSupport
ln -s $HOME/Library/Application\ Support/pipx $HOME/Library/ApplicationSupport/pipx
```

## Does it work to install your package with `pip`?

This is a tip for advanced users. An easy way to check if pipx is the problem or a package you're trying to install is
the problem, is to try installing it using `pip`. For example:

### Unix or macOS

```
python3 -m venv test_venv
test_venv/bin/python3 -m pip install <problem-package>
```

### Windows

```
python -m venv test_venv
test_venv/Scripts/python -m pip install <problem-package>
```

If installation into this "virtual environment" using pip fails, then it's likely that the problem is with the package
or your host system.

To clean up after this experiment:

```
rm -rf test_venv
```

## pipx files not in expected locations according to documentation

pipx versions after 1.2.0 adopt the XDG base directory specification for the location of `PIPX_HOME` and the data,
cache, and log directories. Version 1.2.0 and earlier use `~/.local/pipx` as the default `PIPX_HOME` and install the
data, cache, and log directories under it. To maintain compatibility with older versions, pipx will automatically use
this old `PIPX_HOME` path if it exists. For a map of old and new paths, see [Installation Options](configure-paths.md).

In pipx version 1.5.0, this was reverted for Windows and macOS. It defaults again to `~/.local/pipx` on macOS and to
`~\pipx` on Windows.

If you have a `pipx` version later than 1.2.0 and want to migrate from the old path to the new paths, you can move the
`~/.local/pipx` directory to the new location (after removing cache, log, and trash directories which will get recreated
automatically) and then reinstall all packages.

Please refer to [Moving your pipx installation](move-installation.md) on how to move it.

## Warning: Found a space in the pipx home path

In pipx version 1.5, we introduced the warning you're seeing, as multiple incompatibilities with spaces in the pipx home
path were discovered. You may see this for the following reasons:

1. From pipx version 1.3 to 1.5, we were by default using a path with a space on it on macOS. This unfortunately means
    that all users who installed pipx in this time frame and were using the default behavior are seeing this warning
    now.
1. You set your `PIPX_HOME` to a path with spaces in it explicitly or because your `$HOME` path contains a space.

### Why are spaces in the `PIPX_HOME` path bad

The main reason we can't support paths with spaces is that shebangs don't support spaces in the interpreter path. All
applications installed via `pipx` are installed via `pip`, which creates a script with a shebang at the top, defining
the interpreter of the `venv` to use.

`pip` does some magic to the shebang for scripts defined as a `script`, that resolves this issue. Unfortunately, many
libraries define their scripts as `console_scripts`, where `pip` does not perform this logic. Therefore, these scripts
cannot be run if installed with `pipx` in a path with spaces because the path to the `venv` (and therefore the
interpreter) will contain spaces.

If you want to use a script installed via pipx in a shebang itself (common for example for the AWS CLI), you run into a
similar problem, as the path to the installed script will contain a space.

### How to fix

You can generally fix this by using our default locations, as long as your `$HOME` path does not contain spaces. Please
refer to our [Moving your pipx installation](move-installation.md) docs on how to move the `pipx` installation.

If you're really sure you want to stick to your path with spaces, to suppress the warning set the
`PIPX_HOME_ALLOW_SPACE` environment variable to `true`.


================================================
FILE: docs/how-to/upgrade-pipx.md
================================================
## Upgrade pipx

On macOS:

```
brew update && brew upgrade pipx
```

On Ubuntu Linux:

```
sudo apt upgrade pipx
```

On Fedora Linux:

```
sudo dnf update pipx
```

On Windows:

```
scoop update pipx
```

Otherwise, upgrade via pip:

```
python3 -m pip install --user -U pipx
```

### Note: Upgrading pipx from a pre-0.15.0.0 version to 0.15.0.0 or later

After upgrading to pipx 0.15.0.0 or above from a pre-0.15.0.0 version, you must re-install all packages to take
advantage of the new persistent pipx metadata files introduced in the 0.15.0.0 release. These metadata files store pip
specification values, injected packages, any custom pip arguments, and more in each main package's venv.

If you have no packages installed using the `--spec` option, and no venvs with injected packages, you can do this by
running `pipx reinstall-all`.

If you have any packages installed using the `--spec` option or venvs with injected packages, you should reinstall
packages manually using `pipx uninstall-all`, followed by `pipx install` and possibly `pipx inject`.


================================================
FILE: docs/how-to/use-with-pre-commit.md
================================================
## Using pipx with pre-commit

pipx has [pre-commit](https://pre-commit.com/) support. This lets you run applications:

- that can be run using `pipx run` but don't have native pre-commit support;
- using its prebuilt wheel from pypi.org instead of building it from source; and
- using pipx's `--spec` and `--index-url` flags.

Example configuration for use of the code linter [yapf](https://github.com/google/yapf/). This is to be added to your
`.pre-commit-config.yaml`.

```yaml
  - repo: https://github.com/pypa/pipx
    rev: 1.5.0
    hooks:
      - id: pipx
        alias: yapf
        name: yapf
        args: [yapf, -i]
        types: [python]
```


================================================
FILE: docs/index.md
================================================
<p align="center">
<a href="https://pipx.pypa.io">
<img align="center" src="https://github.com/pypa/pipx/raw/main/logo.svg" width="200"/>
</a>
</p>

# pipx — Install and Run Python Applications in Isolated Environments

<p align="center">
<a href="https://github.com/pypa/pipx/raw/main/pipx_demo.gif">
<img src="https://github.com/pypa/pipx/raw/main/pipx_demo.gif"/>
</a>
</p>

pipx installs and runs end-user Python applications in isolated environments. It fills the same role as macOS's `brew`,
JavaScript's [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b), and Linux's `apt`.
Under the hood it uses pip, but unlike pip it creates a separate virtual environment for each application, keeping your
system clean.

## Documentation

- **[Tutorials](tutorial/index.md)** — install your first application and run commands in temporary environments.
- **[How-to Guides](how-to/index.md)** — recipes for installing pipx, injecting packages, configuring paths, and more.
- **[Reference](reference/index.md)** — CLI flags, examples, environment variables, and programs to try.
- **[Explanation](explanation/index.md)** — how pipx works under the hood and how it compares to other tools.

```mermaid
flowchart LR
    USER["you"] --> |"pipx install"| PIPX["pipx"]
    USER --> |"pipx run"| PIPX
    PIPX --> |"fetches from"| PYPI["PyPI"]
    PIPX --> |"creates"| VENV["isolated venvs"]
    VENV --> |"exposes on PATH"| APPS["black, ruff,<br/>poetry, ..."]

    style USER fill:#3f72af,color:#fff
    style PIPX fill:#2a9d8f,color:#fff
    style PYPI fill:#c78c20,color:#fff
    style VENV fill:#7c4dff,color:#fff
    style APPS fill:#388e3c,color:#fff
```

## pip vs pipx

pip installs both libraries and applications into your current environment with no isolation. pipx installs only
applications, each in its own virtual environment, and exposes their commands on your `PATH`. You get clean uninstalls
and zero dependency conflicts between tools.

## Where do apps come from?

pipx pulls packages from [PyPI](https://pypi.org/) by default, but accepts any source pip supports: local directories,
wheels, and git URLs. Any package that declares
[console script entry points](https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-console-scripts-entry-point)
works with pipx. [Poetry](https://python-poetry.org/docs/pyproject/#scripts) and
[hatch](https://hatch.pypa.io/latest/config/metadata/#cli) users can add entry points the same way.

## Features

pipx lets you

- install CLI apps into isolated environments with `pipx install`, guaranteeing no dependency conflicts and clean
    uninstalls;
- list, upgrade, and uninstall packages in one command; and
- run the latest version of any app in a temporary environment with `pipx run`.

pipx runs with regular user permissions and never calls `sudo pip install`.

## Testimonials

> "Thanks for improving the workflow that pipsi has covered in the past. Nicely done!"
>
> — *[Jannis Leidel](https://twitter.com/jezdez), PSF fellow, former pip and Django core developer, and founder of the
> Python Packaging Authority (PyPA)*

> "My setup pieces together pyenv, poetry, and pipx. [...] For the things I need, it's perfect."
>
> — *[Jacob Kaplan-Moss](https://twitter.com/jacobian), co-creator of Django in blog post
> [My Python Development Environment, 2020 Edition](https://jacobian.org/2019/nov/11/python-environment-2020/)*

> "I'm a big fan of pipx. I think pipx is super cool."
>
> — *[Michael Kennedy](https://twitter.com/mkennedy), co-host of PythonBytes podcast in
> [episode 139](https://pythonbytes.fm/episodes/transcript/139/f-yes-for-the-f-strings)*

## Credits

pipx was inspired by [pipsi](https://github.com/mitsuhiko/pipsi) and [npx](https://github.com/npm/npx). It was created
by [Chad Smith](https://github.com/cs01/) and has had lots of help from
[contributors](https://github.com/pypa/pipx/graphs/contributors). The logo was created by
[@IrishMorales](https://github.com/IrishMorales).

pipx is maintained by a team of volunteers (in alphabetical order)

- [Bernát Gábor](https://github.com/gaborbernat)
- [Chad Smith](https://github.com/cs01) - co-lead maintainer
- [Chrysle](https://github.com/chrysle)
- [Jason Lam](https://github.com/dukecat0)
- [Matthew Clapp](https://github.com/itsayellow) - co-lead maintainer
- [Robert Offner](https://github.com/gitznik)
- [Tzu-ping Chung](https://github.com/uranusjr)


================================================
FILE: docs/reference/environment-variables.md
================================================
## Environment Variables

pipx reads the following environment variables. All are optional.

| Variable                    | Description                                                  | Default                                                                    |
| --------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `PIPX_HOME`                 | Root directory for pipx virtual environments.                | `~/.local/share/pipx` (Linux), `~/.local/pipx` (macOS), `~\pipx` (Windows) |
| `PIPX_BIN_DIR`              | Directory where application entry-point symlinks are placed. | `~/.local/bin`                                                             |
| `PIPX_MAN_DIR`              | Directory where man page symlinks are placed.                | `~/.local/share/man`                                                       |
| `PIPX_GLOBAL_HOME`          | Root directory for global (`--global`) virtual environments. | `/opt/pipx`                                                                |
| `PIPX_GLOBAL_BIN_DIR`       | Binary directory for global installs.                        | `/usr/local/bin`                                                           |
| `PIPX_GLOBAL_MAN_DIR`       | Man page directory for global installs.                      | `/usr/local/share/man`                                                     |
| `PIPX_DEFAULT_PYTHON`       | Python interpreter to use when `--python` is not passed.     | `python3` (or `py` on Windows)                                             |
| `PIPX_SHARED_LIBS`          | Override the shared libraries directory.                     | `PIPX_HOME/shared`                                                         |
| `PIPX_FETCH_MISSING_PYTHON` | Fetch missing Python versions when `--python` is used.       | _(unset)_                                                                  |
| `PIPX_HOME_ALLOW_SPACE`     | Set to `true` to suppress the "space in PIPX_HOME" warning.  | _(unset)_                                                                  |
| `PIPX_USE_EMOJI`            | Set to `0` to disable emoji output.                          | `1`                                                                        |

### Notes

`PIPX_HOME` has platform-specific fallback logic. If a legacy directory (e.g. `~/.local/pipx` on Linux) already exists,
pipx uses it instead of the new default. See [Configure Paths](../how-to/configure-paths.md) for details.

Standard `PIP_*` environment variables (e.g. `PIP_INDEX_URL`) are forwarded to pip when pipx invokes it. See
[Troubleshooting](../how-to/troubleshoot.md#check-for-pip_-environment-variables) if unexpected pip behaviour occurs.

Run `pipx environment` to see the resolved value of every directory variable on your system.


================================================
FILE: docs/reference/examples.md
================================================
## `pipx install` examples

```
pipx install pycowsay
pipx install --python python3.10 pycowsay
pipx install --python 3.12 pycowsay
pipx install --fetch-missing-python --python 3.12 pycowsay
pipx install git+https://github.com/psf/black
pipx install git+https://github.com/psf/black.git@branch-name
pipx install git+https://github.com/psf/black.git@git-hash
pipx install git+ssh://<username>@<private-repo-domain>/<path-to-package.git>
pipx install https://github.com/psf/black/archive/18.9b0.zip
pipx install black[d]
pipx install --preinstall ansible-lint --preinstall mitogen ansible-core
pipx install 'black[d] @ git+https://github.com/psf/black.git@branch-name'
pipx install --suffix @branch-name 'black[d] @ git+https://github.com/psf/black.git@branch-name'
pipx install --include-deps jupyter
pipx install --pip-args='--pre' poetry
pipx install --pip-args='--index-url=<private-repo-host>:<private-repo-port> --trusted-host=<private-repo-host>:<private-repo-port>' private-repo-package
pipx install --index-url https://test.pypi.org/simple/ --pip-args='--extra-index-url https://pypi.org/simple/' some-package
pipx install --global pycowsay
pipx install .
pipx install path/to/some-project
```

## `pipx run` examples

pipx enables you to test various combinations of Python versions and package versions in ephemeral environments:

```
pipx run BINARY  # latest version of binary is run with python3
pipx run --spec PACKAGE==2.0.0 BINARY  # specific version of package is run
pipx run --python python3.10 BINARY  # Installed and invoked with specific Python version
pipx run --python python3.9 --spec PACKAGE=1.7.3 BINARY
pipx run --spec git+https://url.git BINARY  # latest version on default branch is run
pipx run --spec git+https://url.git@branch BINARY
pipx run --spec git+https://url.git@hash BINARY
pipx run pycowsay moo
pipx --version  # prints pipx version
pipx run pycowsay --version  # prints pycowsay version
pipx run --python pythonX pycowsay
pipx run pycowsay==2.0 --version
pipx run pycowsay[dev] --version
pipx run --spec git+https://github.com/psf/black.git black
pipx run --spec git+https://github.com/psf/black.git@branch-name black
pipx run --spec git+https://github.com/psf/black.git@git-hash black
pipx run --spec https://github.com/psf/black/archive/18.9b0.zip black --help
pipx run https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py
```

You can run local files, or scripts hosted on the internet, and you can run them with arguments:

```
pipx run test.py
pipx run test.py 1 2 3
pipx run https://example.com/test.py
pipx run https://example.com/test.py 1 2 3
```

A simple filename is ambiguous - it could be a file, or a package on PyPI. It will be treated as a filename if the file
exists, or as a package if not. To force interpretation as a local path, use `--path`, and to force interpretation as a
package name, use `--spec` (with the PyPI name of the package).

```
pipx run myscript.py # Local file, if myscript.py exists
pipx run doesnotexist.py # Package, because doesnotexist.py is not a local file
pipx run --path test.py # Always a local file
pipx run --spec test-py test.py # Always a package on PyPI
```

You can also run scripts that have dependencies:

If you have a script `test.py` that needs 3rd party libraries, you can add
[inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/) in the style of
PEP 723.

```
# test.py

# /// script
# dependencies = ["requests"]
# ///

import sys
import requests
project = sys.argv[1]
pipx_data = requests.get(f"https://pypi.org/pypi/{project}/json").json()
print(pipx_data["info"]["version"])
```

Then you can run it as follows:

```
> pipx run test.py pipx
1.1.0
```

## `pipx inject` example

One use of the inject command is setting up a REPL with some useful extra packages.

```
> pipx install ptpython
> pipx inject ptpython requests pendulum
```

After running the above commands, you will be able to import and use the `requests` and `pendulum` packages inside a
`ptpython` repl.

Equivalently, the extra packages can be listed in a text file (e.g. `useful-packages.txt`). Each line is a separate
package specifier with the same syntax as the command line. Comments are supported with a `#` prefix. Hence, the syntax
is a strict subset of the pip [requirements file format][pip-requirements] syntax.

```
# Additional packages
requests

pendulum # for easier datetimes
```

This file can then be given to `pipx inject` on the command line:

```shell
> pipx inject ptpython --requirement useful-packages.txt
# or:
> pipx inject ptpython -r useful-packages.txt
```

Note that these options can be repeated and used together, e.g.

```
> pipx inject ptpython package-1 -r extra-packages-1.txt -r extra-packages-2.txt package-2
```

If you require full pip functionality, then use the `runpip` command instead; however, the installed packages won't be
recognised as "injected".

## `pipx list` example

```
> pipx list
venvs are in /Users/user/.local/pipx/venvs
binaries are exposed on your $PATH at /Users/user/.local/bin
   package black 18.9b0, Python 3.10.0
    - black
    - blackd
   package pipx 0.10.0, Python 3.10.0
    - pipx

> pipx list --short
black 18.9b0
pipx 0.10.0
```

## `pipx install-all` example

```shell
> pipx list --json > pipx.json
> pipx install-all pipx.json
'black' already seems to be installed. Not modifying existing installation in '/usr/local/pipx/venvs/black'. Pass '--force' to force installation.
'pipx' already seems to be installed. Not modifying existing installation in '/usr/local/pipx/venvs/black'. Pass '--force' to force installation.
> pipx install-all pipx.json --force
Installing to existing venv 'black'
  installed package black 24.3.0, installed using Python 3.10.12
  These apps are now globally available
    - black
    - blackd
done! ✨ 🌟 ✨
Installing to existing venv 'pipx'
  installed package pipx 1.4.3, installed using Python 3.10.12
  These apps are now globally available
    - pipx
done! ✨ 🌟 ✨
```

## `pipx upgrade-shared` examples

One use of the upgrade-shared command is to force a `pip` upgrade.

```shell
> pipx upgrade-shared
```

This example pins `pip` (temporarily, until the next automatic upgrade, if that is not explicitly turned off) to a
specific version.

```shell
> pipx upgrade-shared --pip-args=pip==24.0
```

## `pipx pin` examples

Pinning keeps an installation at its current version until you call `pipx unpin`.

Pin the entire environment (main package plus anything injected):

```shell
> pipx install httpie
> pipx pin httpie
> pipx list --pinned
httpie <current version>
```

Pin only injected packages so the main package can continue to receive upgrades:

```shell
> pipx inject poetry poetry-plugin-export poetry-plugin-app
> pipx pin poetry --injected-only
Pinned 2 packages in venv poetry
  - poetry-plugin-export <current version>
  - poetry-plugin-app <current version>
```

Skip selected injected packages when pinning:

```shell
> pipx inject pdm pdm-django pdm-pytorch
> pipx pin pdm --skip pdm-django
Pinned 1 packages in venv pdm
  - pdm-pytorch <current version>
```

Unpin everything so upgrades resume:

```shell
> pipx unpin poetry
Unpinned 2 packages in venv poetry
  - poetry
  - poetry-plugin-export
  - poetry-plugin-app
```

[pip-requirements]: https://pip.pypa.io/en/stable/reference/requirements-file-format/


================================================
FILE: docs/reference/index.md
================================================
# Reference

Technical reference for pipx: CLI flags, environment variables, and runnable examples.

- [CLI Reference](cli.md) — auto-generated help text for every pipx command.
- [Examples](examples.md) — copy-paste command examples for install, run, inject, list, and more.
- [Programs to Try](programs-to-try.md) — Python applications worth installing with pipx.
- [Environment Variables](environment-variables.md) — every `PIPX_*` variable and what it controls.


================================================
FILE: docs/reference/programs-to-try.md
================================================
## Programs

Here are some programs you can try out. If you've never used the program before, make sure you add the `--help` flag so
it doesn't do something you don't expect. If you decide you want to install, you can run `pipx install PACKAGE` instead.

### ansible

IT automation

```
pipx install --include-deps ansible
```

### asciinema

Record and share your terminal sessions, the right way.

```
pipx run asciinema
```

### black

uncompromising Python code formatter

```
pipx run black
```

### pybabel

internationalizing and localizing Python applications

```
pipx run --spec=babel pybabel --help
```

### chardetect

detect file encoding

```
pipx run --spec=chardet chardetect --help
```

### cookiecutter

creates projects from project templates

```
pipx run cookiecutter
```

### create-python-package

easily create and publish new Python packages

```
pipx run create-python-package
```

### flake8

tool for style guide enforcement

```
pipx run flake8
```

### gdbgui

browser-based gdb debugger

```
pipx run gdbgui
```

### hatch

Python project manager that lets you build & publish packages, run tasks in environments and more

```
pipx run hatch
```

### hexsticker

create hexagon stickers automatically

```
pipx run hexsticker
```

### ipython

powerful interactive Python shell

```
pipx run ipython
```

### jupyter

web-based notebook environment for interactive computing

```
pipx run jupyter
```

### pipenv

python dependency/environment management

```
pipx run pipenv
```

### poetry

python dependency/environment/packaging management

```
pipx run poetry
```

### pylint

source code analyzer

```
pipx run pylint
```

### pyinstaller

bundles a Python application and all its dependencies into a single package

```
pipx run pyinstaller
```

### pyxtermjs

fully functional terminal in the browser

```
pipx run pyxtermjs
```

### ruff

An extremely fast Python linter

```
pipx run ruff
```

### shell-functools

Functional programming tools for the shell

```
pipx install shell-functools
```


================================================
FILE: docs/tutorial/getting-started.md
================================================
## Getting Started with pipx

This tutorial covers the core pipx workflow: installing an application, running it, and managing it. You need
[pipx installed](../how-to/install-pipx.md) before continuing.

### Install your first application

Pick a small package to try. `pycowsay` works well:

```
pipx install pycowsay
```

pipx creates an isolated virtual environment for `pycowsay`, installs it there, and links the `pycowsay` command into a
directory on your `PATH`. Run it from anywhere:

```
pycowsay "Hello, pipx!"
```

### List installed applications

```
pipx list
```

The output shows the virtual environment location, exposed commands, and the Python version each package uses.

### Run an application without installing

`pipx run` executes an application in a temporary environment and cleans up after itself:

```
pipx run pycowsay moooo!
```

### Upgrade an installed application

```
pipx upgrade pycowsay
```

Or upgrade everything at once:

```
pipx upgrade-all
```

### Uninstall an application

```
pipx uninstall pycowsay
```

pipx deletes the isolated environment and removes the command from your `PATH`.

### Next steps

Continue with the [install applications](install-applications.md) and [run applications](run-applications.md) tutorials
for a closer look at the two core commands. The [how-to guides](../how-to/index.md) cover tasks like injecting packages
and configuring paths. The full [CLI reference](../reference/cli.md) documents every flag.


================================================
FILE: docs/tutorial/index.md
================================================
# Tutorials

Step-by-step lessons that take you from zero to productive with pipx. Start from the top if you are new.

- [Getting Started](getting-started.md) — install your first application and learn the core commands.
- [Install Applications](install-applications.md) — `pipx install` with real examples.
- [Run Applications](run-applications.md) — `pipx run` for one-off execution in temporary environments.


================================================
FILE: docs/tutorial/install-applications.md
================================================
## Installing a Package and its Applications

Install an application with:

```
pipx install PACKAGE
```

pipx creates a virtual environment, installs the package, and adds its entry points to a location on your `PATH`.
`pipx install pycowsay` makes the `pycowsay` command available system-wide while sandboxing pycowsay in its own virtual
environment. No `sudo` required. To install for all users on the system, pass `--global` after the subcommand (see
[Configure Paths](../how-to/configure-paths.md#-global-argument)).

```
>> pipx install pycowsay
  installed package pycowsay 2.0.3, Python 3.10.3
  These apps are now globally available
    - pycowsay
done! ✨ 🌟 ✨


>> pipx list
venvs are in /home/user/.local/share/pipx/venvs
apps are exposed on your $PATH at /home/user/.local/bin
   package pycowsay 2.0.3, Python 3.10.3
    - pycowsay


# Now you can run pycowsay from anywhere
>> pycowsay mooo
  ____
< mooo >
  ====
         \
          \
            ^__^
            (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

```


================================================
FILE: docs/tutorial/run-applications.md
================================================
## Running an Application in a Temporary Virtual Environment

`pipx run` downloads and runs Python applications in a one-time, temporary environment, then leaves your system
untouched. Use it to initialize a new project, check an app's help text, or try a tool without committing to an install.

The blog post [How to set up a perfect Python project](https://sourcery.ai/blog/python-best-practices/) uses `pipx run`
to kickstart a new project with [cookiecutter](https://github.com/cookiecutter/cookiecutter).

```
pipx run APP [ARGS...]
```

pipx installs the package in an isolated, temporary directory and invokes the app:

```
> pipx run pycowsay moo

  ---
< moo >
  ---
   \   ^__^
    \  (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||


```

Arguments after the application name pass straight through:

```
> pipx run pycowsay these arguments are all passed to pycowsay!

  -------------------------------------------
< these arguments are all passed to pycowsay! >
  -------------------------------------------
   \   ^__^
    \  (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||

```

pipx caches virtual environments per app for a few days. After they expire, pipx fetches the latest version.

### Ambiguous arguments

pipx can consume arguments meant for the application:

```
> pipx run pycowsay --py

usage: pipx run [-h] [--no-cache] [--pypackages] [--spec SPEC] [--verbose] [--python PYTHON]
                [--system-site-packages] [--index-url INDEX_URL] [--editable] [--pip-args PIP_ARGS]
                app ...
pipx run: error: ambiguous option: --py could match --pypackages, --python
```

Place `--` before the app name to forward all arguments verbatim:

```
> pipx run -- pycowsay --py


  ----
< --py >
  ----
   \   ^__^
    \  (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||


```

### App name differs from package name

Use `--spec` to specify the package and provide the app name separately:

```
pipx run --spec PACKAGE APP
```

The [esptool](https://github.com/espressif/esptool) package, for example, exposes executables with different names:

```
>> pipx run esptool
'esptool' executable script not found in package 'esptool'.
Available executable scripts:
    esp_rfc2217_server.py - usage: 'pipx run --spec esptool esp_rfc2217_server.py [arguments?]'
    espefuse.py - usage: 'pipx run --spec esptool espefuse.py [arguments?]'
    espsecure.py - usage: 'pipx run --spec esptool espsecure.py [arguments?]'
    esptool.py - usage: 'pipx run --spec esptool esptool.py [arguments?]'
```

Run them with `--spec`:

```
pipx run --spec esptool esptool.py
```

The `.py` is part of the executable name as declared by the package. The
[pymodbus](https://github.com/pymodbus-dev/pymodbus) package shows a similar pattern:

```
pipx run --spec pymodbus[repl] pymodbus.console
pipx run --spec pymodbus[repl] pymodbus.server
pipx run --spec pymodbus[repl] pymodbus.simulator
```

Package authors can avoid this `--spec` requirement by declaring a
[`pipx.run` entry point](../explanation/making-packages-compatible.md#the-pipxrun-entry-point-group) in their package
metadata.


================================================
FILE: get-pipx.py
================================================
#!/usr/bin/env python3
import sys


def fail(msg):
    sys.stderr.write(msg + "\n")
    sys.stderr.flush()
    sys.exit(1)


def main():
    fail(
        "This installation method has been deprecated. "
        "See https://github.com/pypa/pipx for current installation "
        "instructions."
    )


if __name__ == "__main__":
    main()


================================================
FILE: mkdocs.yml
================================================
site_name: pipx
site_description: execute binaries from Python packages in isolated environments
theme:
  name: "material"
  palette:
    - media: "(prefers-color-scheme: light)"
      scheme: default
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode
    - media: "(prefers-color-scheme: dark)"
      scheme: slate
      toggle:
        icon: material/brightness-4
        name: Switch to light mode
  features:
    - navigation.tabs
    - navigation.tabs.sticky
    - navigation.indexes
    - toc.integrate
    - content.tabs.link
    - content.code.copy
repo_name: pypa/pipx
repo_url: https://github.com/pypa/pipx
edit_uri: edit/main/docs/
extra:
  analytics:
    provider: 'google'
    property: 'UA-90243909-2'
nav:
  - Home: "index.md"
  - Tutorials:
      - tutorial/index.md
      - Getting Started: "tutorial/getting-started.md"
      - Install Applications: "tutorial/install-applications.md"
      - Run Applications: "tutorial/run-applications.md"
  - How-to Guides:
      - how-to/index.md
      - Install pipx: "how-to/install-pipx.md"
      - Upgrade pipx: "how-to/upgrade-pipx.md"
      - Inject Packages: "how-to/inject-packages.md"
      - Pin Packages: "how-to/pin-packages.md"
      - Run Scripts: "how-to/run-scripts.md"
      - Use with pre-commit: "how-to/use-with-pre-commit.md"
      - Configure Paths: "how-to/configure-paths.md"
      - Move Installation: "how-to/move-installation.md"
      - Shell Completions: "how-to/shell-completions.md"
      - Troubleshoot: "how-to/troubleshoot.md"
  - Reference:
      - reference/index.md
      - CLI Reference: "reference/cli.md"
      - Examples: "reference/examples.md"
      - Programs to Try: "reference/programs-to-try.md"
      - Environment Variables: "reference/environment-variables.md"
  - Explanation:
      - explanation/index.md
      - How pipx Works: "explanation/how-pipx-works.md"
      - Comparisons: "explanation/comparisons.md"
      - Making Packages Compatible: "explanation/making-packages-compatible.md"
  - Contributing: "contributing.md"
  - Changelog: "changelog.md"
markdown_extensions:
  - markdown_gfm_admonition # GitHub's admonition (alert) syntax
  - footnotes
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
  - pymdownx.tabbed:
      alternate_style: true
plugins:
  - search:
      lang: en
  - gen-files:
      scripts:
        - scripts/gen_doc_pages.py
  - macros
exclude_docs: |
  README.md


================================================
FILE: pyproject.toml
================================================
[build-system]
build-backend = "hatchling.build"
requires = [
  "hatch-vcs>=0.4",
  "hatchling>=1.27",
]

[project]
name = "pipx"
description = "Install and Run Python Applications in Isolated Environments"
readme = "docs/README.md"
keywords = [
  "cli",
  "install",
  "pip",
  "Virtual Environment",
  "workflow",
]
license = "MIT"
license-files = [ "LICENSE" ]
authors = [
  { name = "Chad Smith", email = "chadsmith.software@gmail.com" },
]
requires-python = ">=3.9"
classifiers = [
  "Operating System :: OS Independent",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
]
dynamic = [
  "version",
]
dependencies = [
  "argcomplete>=1.9.4",
  "colorama>=0.4.4; sys_platform=='win32'",
  "packaging>=20",
  "platformdirs>=2.1",
  "tomli; python_version<'3.11'",
  "userpath>=1.6,!=1.9",
]
urls."Issue Tracker" = "https://github.com/pypa/pipx/issues"
urls."Release Notes" = "https://pipx.pypa.io/latest/changelog/"
urls."Source Code" = "https://github.com/pypa/pipx"
urls.Documentation = "https://pipx.pypa.io"
urls.Homepage = "https://pipx.pypa.io"
scripts.pipx = "pipx.main:cli"

[dependency-groups]
dev = [
  { include-group = "docs" },
  { include-group = "lint" },
  { include-group = "man" },
  { include-group = "test" },
]
test = [
  "pypiserver[cache,passlib]>=2.3.2",
  "pytest>=8",
  "pytest-cov>=4",
  "pytest-mock>=3.11.1",
  "pytest-xdist>=3.5",
  "setuptools>=68; python_version>='3.12'",
  "watchdog>=4",
]
docs = [
  "jinja2",
  "markdown-gfm-admonition",
  "mkdocs<2",
  "mkdocs-gen-files",
  "mkdocs-macros-plugin",
  "mkdocs-material",
  "towncrier>=23.6",
]
lint = [
  "pre-commit>=3",
]
man = [
  "argparse-manpage[setuptools]",
]
zipapp = [
  "shiv>=1.0.6",
]

[tool.hatch]
build.hooks.vcs.version-file = "src/pipx/version.py"
build.targets.sdist.include = [
  "/src",
  "/logo.png",
  "/pipx_demo.gif",
  "/*.md",
]
version.source = "vcs"

[tool.ruff]
line-length = 121
lint.extend-select = [
  "A",
  "B",
  "C4",
  "C9",
  "I",
  "ISC",
  "PERF",
  "PGH",
  "PIE",
  "PLC",
  "PLE",
  "PLW",
  "RSE",
  "RUF",
  "TC",
  "UP",
  "W",
]
lint.ignore = [
  "PERF203",
  "PLW1508",
  "RUF005",
]
lint.per-file-ignores."src/pipx/venv.py" = [
  "A005",
]
lint.isort = { known-first-party = [
  "helpers",
  "package_info",
  "pipx",
] }
lint.mccabe.max-complexity = 15

[tool.codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = ".git,*.pdf,*.svg,.nox,testdata,.mypy_cache"
check-hidden = true
# case sensitive etc
ignore-regex = "\\b(UE|path/doesnt/exist)\\b"

[tool.mypy]
overrides = [ { module = [
  "pycowsay.*",
], ignore_missing_imports = true } ]
warn_unreachable = true
enable_error_code = [ "ignore-without-code", "redundant-expr", "truthy-bool" ]

[tool.pytest]
ini_options.minversion = "8"
ini_options.log_cli_level = "INFO"
ini_options.markers = [
  "all_packages: test install with maximum number of packages",
]
ini_options.testpaths = [ "tests" ]
ini_options.addopts = [ "-ra", "--strict-config", "--strict-markers" ]

[tool.towncrier]
directory = "changelog.d"
filename = "docs/changelog.md"
start_string = "<!-- towncrier release notes start -->\n"
underlines = [
  "",
  "",
  "",
]
title_format = "## [{version}](https://github.com/pypa/pipx/tree/{version}) - {project_date}"
issue_format = "[#{issue}](https://github.com/pypa/pipx/issues/{issue})"
package = "pipx"


================================================
FILE: scripts/gen_doc_pages.py
================================================
import os
import sys
from pathlib import Path
from subprocess import check_output
from typing import Optional

import mkdocs_gen_files
from jinja2 import Environment, FileSystemLoader


def get_help(cmd: Optional[str]) -> str:
    base = ["pipx"]
    args = base + ([cmd] if cmd else []) + ["--help"]
    env_patch = os.environ.copy()
    env_patch["PATH"] = os.pathsep.join([str(Path(sys.executable).parent)] + env_patch["PATH"].split(os.pathsep))
    content = check_output(args, text=True, env=env_patch)
    content = content.replace(str(Path("~").expanduser()), "~")
    return f"""
```
{content}
```
"""


params = {
    "install": get_help("install"),
    "installall": get_help("install-all"),
    "uninject": get_help("uninject"),
    "inject": get_help("inject"),
    "upgrade": get_help("upgrade"),
    "upgradeall": get_help("upgrade-all"),
    "upgradeshared": get_help("upgrade-shared"),
    "uninstall": get_help("uninstall"),
    "uninstallall": get_help("uninstall-all"),
    "reinstall": get_help("reinstall"),
    "reinstallall": get_help("reinstall-all"),
    "list": get_help("list"),
    "interpreter": get_help("interpreter"),
    "run": get_help("run"),
    "runpip": get_help("runpip"),
    "ensurepath": get_help("ensurepath"),
    "environment": get_help("environment"),
    "completions": get_help("completions"),
    "usage": get_help(None),
}


env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))

with mkdocs_gen_files.open("reference/cli.md", "wt") as file_handler:
    file_handler.write(env.get_template("docs.md").render(**params))
    file_handler.write("\n")


================================================
FILE: scripts/generate_man.py
================================================
#!/usr/bin/env python3

import os.path
import sys
import textwrap
from typing import cast

from build_manpages.manpage import Manpage  # type: ignore[import-not-found]

from pipx.main import get_command_parser


def main():
    sys.argv[0] = "pipx"
    parser, _ = get_command_parser()
    parser.man_short_description = cast("str", parser.description).splitlines()[1]  # type: ignore[attr-defined]

    manpage = Manpage(parser)
    body = str(manpage)

    # Avoid hardcoding build paths in manpages (and improve readability)
    body = body.replace(os.path.expanduser("~").replace("-", "\\-"), "~")

    # Add a credit section
    body += textwrap.dedent(
        """
        .SH AUTHORS
        .IR pipx (1)
        was written by Chad Smith and contributors.
        The project can be found online at
        .UR https://pipx.pypa.io
        .UE
        .SH SEE ALSO
        .IR pip (1),
        .IR virtualenv (1)
        """
    )

    with open("pipx.1", "w") as f:
        f.write(body)


if __name__ == "__main__":
    main()


================================================
FILE: scripts/list_test_packages.py
================================================
#!/usr/bin/env python3
import argparse
import re
import subprocess
import sys
import tempfile
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any

from test_packages_support import get_platform_list_path


def process_command_line(argv: list[str]) -> argparse.Namespace:
    """Process command line invocation arguments and switches.

    Args:
        argv: list of arguments, or `None` from ``sys.argv[1:]``.

    Returns:
        argparse.Namespace: named attributes of arguments and switches
    """
    # script_name = argv[0]
    argv = argv[1:]

    # initialize the parser object:
    parser = argparse.ArgumentParser(
        description="Create list of needed test packages for pipx tests and local pypiserver."
    )

    # specifying nargs= puts outputs of parser in list (even if nargs=1)

    # required arguments
    parser.add_argument(
        "primary_package_list",
        help="Main packages to examine, getting list of matching distribution files and dependencies.",
    )
    parser.add_argument("package_list_dir", help="Directory to output package distribution lists.")

    # switches/options:
    parser.add_argument("-v", "--verbose", action="store_true", help="Maximum verbosity, especially for pip operations.")

    return parser.parse_args(argv)


def parse_package_list(package_list_file: Path) -> list[dict[str, Any]]:
    output_list: list[dict[str, Any]] = []
    try:
        with package_list_file.open("r") as package_list_fh:
            for line in package_list_fh:
                line_parsed = re.sub(r"#.+$", "", line)
                if not re.search(r"\S", line_parsed):
                    continue
                line_list = line_parsed.strip().split()
                if len(line_list) == 1:
                    output_list.append({"spec": line_list[0]})
                elif len(line_list) == 2:
                    output_list.append({"spec": line_list[0], "no-deps": line_list[1].lower() == "true"})
                else:
                    print(f"ERROR: Unable to parse primary package list line:\n    {line.strip()}")
                    return []
    except OSError:
        print("ERROR: File problem reading primary package list.")
        return []
    return output_list


def create_test_packages_list(package_list_dir_path: Path, primary_package_list_path: Path, verbose: bool) -> int:
    exit_code = 0
    package_list_dir_path.mkdir(exist_ok=True)
    platform_package_list_path = get_platform_list_path(package_list_dir_path)

    primary_test_packages = parse_package_list(primary_package_list_path)
    if not primary_test_packages:
        print(f"ERROR: Problem reading {primary_package_list_path}.  Exiting.", file=sys.stderr)
        return 1

    with ThreadPoolExecutor(max_workers=12) as pool:
        futures = {pool.submit(download, pkg, verbose) for pkg in primary_test_packages}
        downloaded_list = set()
        for future in as_completed(futures):
            downloaded_list.update(future.result())

    all_packages = []
    for downloaded_path in downloaded_list:
        wheel_re = re.search(r"([^-]+)-([^-]+)-([^-]+)\-([^-]+)-([^-]+)(-[^-]+)?\.whl$", downloaded_path)
        src_re = re.search(r"(.+)-([^-]+)\.(?:tar.gz|zip)$", downloaded_path)
        if wheel_re:
            package_name = wheel_re.group(1)
            package_version = wheel_re.group(2)
        elif src_re:
            package_name = src_re.group(1)
            package_version = src_re.group(2)
        else:
            print(f"ERROR: cannot parse: {downloaded_path}", file=sys.stderr)
            continue

        all_packages.append(f"{package_name}=={package_version}")

    with platform_package_list_path.open("w") as package_list_fh:
        for package in sorted(all_packages):
            print(package, file=package_list_fh)

    return exit_code


def download(test_package: dict[str, str], verbose: bool) -> set[str]:
    no_deps = test_package.get("no-deps", False)
    test_package_option_string = " (no-deps)" if no_deps else ""
    verbose_this_iteration = False
    with tempfile.TemporaryDirectory() as download_dir:
        cmd_list = ["pip", "download"] + (["--no-deps"] if no_deps else []) + [test_package["spec"], "-d", download_dir]
        if verbose:
            print(f"CMD: {' '.join(cmd_list)}")
        pip_download_process = subprocess.run(cmd_list, capture_output=True, text=True, check=False)
        if pip_download_process.returncode == 0:
            print(f"Examined {test_package['spec']}{test_package_option_string}")
        else:
            print(f"ERROR with {test_package['spec']}{test_package_option_string}", file=sys.stderr)
            verbose_this_iteration = True
        if verbose or verbose_this_iteration:
            print(pip_download_process.stdout)
            print(pip_download_process.stderr)
        return {i.name for i in Path(download_dir).iterdir()}


def main(argv: list[str]) -> int:
    args = process_command_line(argv)

    return create_test_packages_list(Path(args.package_list_dir), Path(args.primary_package_list), args.verbose)


if __name__ == "__main__":
    try:
        status = main(sys.argv)
    except KeyboardInterrupt:
        print("Stopped by Keyboard Interrupt", file=sys.stderr)
        status = 130

    sys.exit(status)


================================================
FILE: scripts/migrate_pipsi_to_pipx.py
================================================
#!/usr/bin/env python3

"""
Script to migrate from pipsi to pipx
"""

import os
import subprocess
import sys
from pathlib import Path
from shutil import which


def main():
    if not which("pipx"):
        sys.exit("pipx must be installed to migrate from pipsi to pipx")

    if not sys.stdout.isatty():
        sys.exit("Must be run from a terminal, not a script")

    pipsi_home = os.environ.get("PIPSI_HOME", os.path.expanduser("~/.local/venvs/"))
    packages = [p.name for p in Path(pipsi_home).iterdir()]

    if not packages:
        print("No packages installed with pipsi")
        sys.exit(0)

    print("Attempting to migrate the following packages from pipsi to pipx:")
    for package in packages:
        print(f"  - {package}")

    answer = None
    while answer not in ["y", "n"]:
        answer = input("Continue? [y/n] ")

    if answer == "n":
        sys.exit(0)

    error = False
    for package in packages:
        ret = subprocess.run(["pipsi", "uninstall", "--yes", package], check=False)
        if ret.returncode:
            error = True
            print(f"Failed to uninstall {package!r} with pipsi. Not attempting to install with pipx.")
        else:
            print(f"uninstalled {package!r} with pipsi. Now attempting to install with pipx.")
            ret = subprocess.run(["pipx", "install", package], check=False)
            if ret.returncode:
                error = True
                print(f"Failed to install {package!r} with pipx.")
            else:
                print(f"Successfully installed {package} with pipx")

    print(f"Done migrating {len(packages)} packages!")
    print("You may still need to run `pipsi uninstall pipsi` or `pip uninstall pipsi`. Refer to pipsi's documentation.")

    if error:
        print("Note: Finished with errors. Review output to manually complete migration.")


if __name__ == "__main__":
    main()


================================================
FILE: scripts/release.py
================================================
"""Handles creating a release."""

from __future__ import annotations

from pathlib import Path
from subprocess import call, check_call

from git import Commit, Remote, Repo, TagReference  # type: ignore[import-not-found]
from packaging.version import Version

ROOT_DIR = Path(__file__).resolve().parents[1]
CHANGELOG_DIR = ROOT_DIR / "changelog.d"


def main(version_str: str, *, push: bool) -> None:
    repo = Repo(str(ROOT_DIR))
    if repo.is_dirty():
        msg = "Current repository is dirty. Please commit any changes and try again."
        raise RuntimeError(msg)
    remote = get_remote(repo)
    remote.fetch()
    version = resolve_version(version_str, repo)
    print(f"Releasing {version}")
    release_commit = release_changelog(repo, version)
    tag = tag_release_commit(release_commit, repo, version)
    if push:
        print("Pushing release commit")
        repo.git.push(remote.name, "HEAD:main")
        print("Pushing release tag")
        repo.git.push(remote.name, tag)
    print("All done! ✨ 🍰 ✨")


def resolve_version(version_str: str, repo: Repo) -> Version:
    if version_str not in {"auto", "major", "minor", "patch"}:
        return Version(version_str)
    latest_tag = repo.git.describe("--tags", "--abbrev=0")
    parts = [int(x) for x in latest_tag.split(".")]
    if version_str == "major":
        parts = [parts[0] + 1, 0, 0]
    elif version_str == "minor":
        parts = [parts[0], parts[1] + 1, 0]
    elif version_str == "patch":
        parts[2] += 1
    elif any(CHANGELOG_DIR.glob("*.feature.md")) or any(CHANGELOG_DIR.glob("*.removal.md")):
        parts = [parts[0], parts[1] + 1, 0]
    else:
        parts[2] += 1
    return Version(".".join(str(p) for p in parts))


def get_remote(repo: Repo) -> Remote:
    upstream_remote = "pypa/pipx"
    urls = set()
    for remote in repo.remotes:
        for url in remote.urls:
            if url.rstrip(".git").endswith(upstream_remote):
                return remote
            urls.add(url)
    msg = f"Could not find {upstream_remote} remote, has {urls}"
    raise RuntimeError(msg)


def release_changelog(repo: Repo, version: Version) -> Commit:
    print("Generating release commit")
    check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_DIR))
    call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_DIR))
    repo.git.add(".")
    check_call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_DIR))
    return repo.index.commit(f"Release {version}")


def tag_release_commit(release_commit: Commit, repo: Repo, version: Version) -> TagReference:
    print("Tagging release commit")
    existing_tags = [x.name for x in repo.tags]
    if str(version) in existing_tags:
        print(f"Deleting existing tag {version}")
        repo.delete_tag(str(version))
    print(f"Creating tag {version}")
    return repo.create_tag(str(version), ref=release_commit, force=True)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(prog="release")
    parser.add_argument("--version", default="auto")
    parser.add_argument("--no-push", action="store_true")
    options = parser.parse_args()
    main(options.version, push=not options.no_push)


================================================
FILE: scripts/templates/docs.md
================================================
{{usage}}

### pipx install

{{install}}

### pipx install-all

{{installall}}

### pipx uninject

{{uninject}}

### pipx inject

{{inject}}

### pipx upgrade

{{upgrade}}

### pipx upgrade-all

{{upgradeall}}

### pipx upgrade-shared

{{upgradeshared}}

### pipx uninstall

{{uninstall}}

### pipx uninstall-all

{{uninstallall}}

### pipx reinstall

{{reinstall}}

### pipx reinstall-all

{{reinstallall}}

### pipx list

{{list}}

### pipx interpreter

{{interpreter}}

### pipx run

{{run}}

### pipx runpip

{{runpip}}

### pipx ensurepath

{{ensurepath}}

### pipx environment

{{environment}}

### pipx completions

{{completions}}


================================================
FILE: scripts/test_packages_support.py
================================================
import platform
import sys
from pathlib import Path

PYTHON_VERSION_STR = f"{sys.version_info[0]}.{sys.version_info[1]}"

# Platform logic
if sys.platform == "darwin":
    FULL_PLATFORM = "macos" + platform.release().split(".")[0]
elif sys.platform == "win32":
    FULL_PLATFORM = "win"
else:
    FULL_PLATFORM = "unix"


def get_platform_list_path(package_list_dir_path: Path) -> Path:
    return package_list_dir_path / f"{FULL_PLATFORM}-python{PYTHON_VERSION_STR}.txt"


def get_platform_packages_dir_path(pipx_package_cache_path: Path) -> Path:
    return pipx_package_cache_path / f"{PYTHON_VERSION_STR}"


================================================
FILE: scripts/update_package_cache.py
================================================
#!/usr/bin/env python3
import argparse
import re
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

from list_test_packages import create_test_packages_list
from test_packages_support import get_platform_list_path, get_platform_packages_dir_path


def process_command_line(argv: list[str]) -> argparse.Namespace:
    """Process command line invocation arguments and switches.

    Args:
        argv: list of arguments, or `None` from ``sys.argv[1:]``.

    Returns:
        argparse.Namespace: named attributes of arguments and switches
    """
    # script_name = argv[0]
    argv = argv[1:]

    # initialize the parser object:
    parser = argparse.ArgumentParser(
        description="Check and update as needed the pipx tests package cache "
        "for use with the pipx tests local pypiserver."
    )

    # specifying nargs= puts outputs of parser in list (even if nargs=1)

    # required arguments
    parser.add_argument(
        "package_list_dir",
        help="Directory where platform- and python-specific package lists are found for pipx tests.",
    )
    parser.add_argument(
        "pipx_package_cache_dir",
        help="Directory to store the packages distribution files.",
    )

    # switches/options:
    parser.add_argument(
        "-c",
        "--check-only",
        action="store_true",
        help="Only check to see if needed packages are in PACKAGES_DIR, do not download or delete files.",
    )

    return parser.parse_args(argv)


def update_test_packages_cache(package_list_dir_path: Path, pipx_package_cache_path: Path, check_only: bool) -> int:
    exit_code = 0

    platform_package_list_path = get_platform_list_path(package_list_dir_path)
    packages_dir_path = get_platform_packages_dir_path(pipx_package_cache_path)
    packages_dir_path.mkdir(exist_ok=True, parents=True)

    packages_dir_files = list(packages_dir_path.iterdir())

    if not platform_package_list_path.exists():
        print(
            f"WARNING.  File {platform_package_list_path!s}\n    does not exist.  Creating now...",
            file=sys.stderr,
        )
        create_list_returncode = create_test_packages_list(
            package_list_dir_path,
            package_list_dir_path / "primary_packages.txt",
            verbose=False,
        )
        if create_list_returncode == 0:
            print(
                f"File {platform_package_list_path!s}\n"
                "    successfully created.  Please check this file in to the"
                "    repository for future use.",
                file=sys.stderr,
            )
        else:
            print(
                f"ERROR.  Unable to create {platform_package_list_path!s}\n    Cannot continue.\n",
                file=sys.stderr,
            )
            return 1

    try:
        platform_package_list_fh = platform_package_list_path.open("r")
    except OSError:
        print(
            f"ERROR.  File {platform_package_list_path!s}\n    is not readable.  Cannot continue.\n",
            file=sys.stderr,
        )
        return 1
    else:
        platform_package_list_fh.close()

    print("Using the following file to specify needed package files:")
    print(f"    {platform_package_list_path!s}")
    print("Ensuring the following directory contains necessary package files:")
    print(f"    {packages_dir_path!s}")

    packages_dir_hits = []
    packages_dir_missing = []
    with platform_package_list_path.open("r") as platform_package_list_fh:
        for line in platform_package_list_fh:
            package_spec = line.strip()
            package_spec_re = re.search(r"^(.+)==(.+)$", package_spec)
            if not package_spec_re:
                print(f"ERROR: CANNOT PARSE {package_spec}", file=sys.stderr)
                exit_code = 1
                continue

            package_name = package_spec_re.group(1)
            package_ver = package_spec_re.group(2)
            package_dist_patt = re.escape(package_name) + r"-" + re.escape(package_ver) + r"(.tar.gz|.zip|-)"
            matches = [
                output_dir_file
                for output_dir_file in packages_dir_files
                if re.search(package_dist_patt, output_dir_file.name)
            ]
            if len(matches) == 1:
                packages_dir_files.remove(matches[0])
                packages_dir_hits.append(matches[0])
                continue
            elif len(matches) > 1:
                print(f"ERROR: more than one match for {package_spec}.", file=sys.stderr)
                print(f"    {matches}", file=sys.stderr)
                exit_code = 1
                continue

            packages_dir_missing.append(package_spec)

    print(f"MISSING FILES: {len(packages_dir_missing)}")
    print(f"EXISTING (found) FILES: {len(packages_dir_hits)}")
    print(f"LEFTOVER (unused) FILES: {len(packages_dir_files)}")

    if check_only:
        return 0 if len(packages_dir_missing) == 0 else 1
    else:
        with ThreadPoolExecutor(max_workers=4) as pool:
            futures = {pool.submit(download, pkg, packages_dir_path) for pkg in packages_dir_missing}
            for future in as_completed(futures):
                exit_code = future.result() or exit_code

        for unused_file in packages_dir_files:
            print(f"Deleting {unused_file}...")
            unused_file.unlink()

    return exit_code


def download(package_spec: str, packages_dir_path: Path) -> int:
    pip_download_process = subprocess.run(
        [
            sys.executable,
            "-m",
            "pip",
            "download",
            "--no-deps",
            package_spec,
            "-d",
            str(packages_dir_path),
        ],
        capture_output=True,
        text=True,
        check=False,
    )
    if pip_download_process.returncode == 0:
        print(f"Successfully downloaded {package_spec}")
        return 0

    print(f"ERROR downloading {package_spec}", file=sys.stderr)
    print(pip_download_process.stdout, file=sys.stderr)
    print(pip_download_process.stderr, file=sys.stderr)
    return 1


def main(argv: list[str]) -> int:
    args = process_command_line(argv)
    return update_test_packages_cache(Path(args.package_list_dir), Path(args.pipx_package_cache_dir), args.check_only)


if __name__ == "__main__":
    try:
        status = main(sys.argv)
    except KeyboardInterrupt:
        print("Stopped by Keyboard Interrupt", file=sys.stderr)
        status = 130

    sys.exit(status)


================================================
FILE: src/pipx/__init__.py
================================================
import sys

if sys.version_info < (3, 9, 0):  # noqa: UP036
    sys.exit("Python 3.9 or later is required. See https://github.com/pypa/pipx for installation instructions.")


================================================
FILE: src/pipx/__main__.py
================================================
import os
import sys

if not __spec__ or not __spec__.parent:
    # Running from source. Add pipx's source code to the system
    # path to allow direct invocation, such as:
    #   python src/pipx --help
    pipx_package_source_path = os.path.dirname(os.path.dirname(__file__))
    sys.path.insert(0, pipx_package_source_path)

from pipx.main import cli

if __name__ == "__main__":
    sys.exit(cli())


================================================
FILE: src/pipx/animate.py
================================================
import logging
import shutil
import sys
from collections.abc import Generator
from contextlib import contextmanager
from threading import Event, Thread

from pipx.constants import WINDOWS
from pipx.emojis import EMOJI_SUPPORT

logger = logging.getLogger(__name__)

stderr_is_tty = bool(sys.stderr and sys.stderr.isatty())

CLEAR_LINE = "\033[K"
EMOJI_ANIMATION_FRAMES = ["⣷", "⣯", "⣟", "⡿", "⢿", "⣻", "⣽", "⣾"]
NONEMOJI_ANIMATION_FRAMES = ["", ".", "..", "..."]
EMOJI_FRAME_PERIOD = 0.1
NONEMOJI_FRAME_PERIOD = 1
MINIMUM_COLS_ALLOW_ANIMATION = 16


if WINDOWS:
    import ctypes

    class _CursorInfo(ctypes.Structure):
        _fields_ = (("size", ctypes.c_int), ("visible", ctypes.c_byte))


def _env_supports_animation() -> bool:
    (term_cols, _) = shutil.get_terminal_size(fallback=(0, 0))
    return stderr_is_tty and term_cols > MINIMUM_COLS_ALLOW_ANIMATION


@contextmanager
def animate(message: str, do_animation: bool, *, delay: float = 0) -> Generator[None, None, None]:
    pipx_logger = logging.getLogger("pipx")
    handler_level = pipx_logger.handlers[0].level if pipx_logger.handlers else 0
    if pipx_logger.handlers and handler_level > logging.WARNING:
        yield
        return

    if not do_animation or not _env_supports_animation():
        sys.stderr.write(f"{message}...\n")
        yield
        return

    event = Event()

    if EMOJI_SUPPORT:
        animate_at_beginning_of_line = True
        symbols = EMOJI_ANIMATION_FRAMES
        period = EMOJI_FRAME_PERIOD
    else:
        animate_at_beginning_of_line = False
        symbols = NONEMOJI_ANIMATION_FRAMES
        period = NONEMOJI_FRAME_PERIOD

    thread_kwargs = {
        "message": message,
        "event": event,
        "symbols": symbols,
        "delay": delay,
        "period": period,
        "animate_at_beginning_of_line": animate_at_beginning_of_line,
    }

    t = Thread(target=print_animation, kwargs=thread_kwargs)
    t.start()

    try:
        yield
    finally:
        event.set()
        clear_line()


def print_animation(
    *,
    message: str,
    event: Event,
    symbols: list[str],
    delay: float,
    period: float,
    animate_at_beginning_of_line: bool,
) -> None:
    (term_cols, _) = shutil.get_terminal_size(fallback=(9999, 24))
    event.wait(delay)
    while not event.wait(0):
        for s in symbols:
            if animate_at_beginning_of_line:
                max_message_len = term_cols - len(f"{s} ... ")
                cur_line = f"{s} {message:.{max_message_len}}"
                if len(message) > max_message_len:
                    cur_line += "..."
            else:
                max_message_len = term_cols - len("... ")
                cur_line = f"{message:.{max_message_len}}{s}"

            clear_line()
            sys.stderr.write(cur_line)
            sys.stderr.flush()
            if event.wait(period):
                break


# for Windows pre-ANSI-terminal-support (before Windows 10 TH2 (v1511))
# https://stackoverflow.com/a/10455937
def win_cursor(visible: bool) -> None:
    if sys.platform != "win32":  # hello mypy
        return
    ci = _CursorInfo()  # type: ignore[unreachable]
    handle = ctypes.windll.kernel32.GetStdHandle(-11)
    ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
    ci.visible = visible
    ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))


def hide_cursor() -> None:
    if stderr_is_tty:
        if WINDOWS:
            win_cursor(visible=False)
        else:
            sys.stderr.write("\033[?25l")
            sys.stderr.flush()


def show_cursor() -> None:
    if stderr_is_tty:
        if WINDOWS:
            win_cursor(visible=True)
        else:
            sys.stderr.write("\033[?25h")
            sys.stderr.flush()


def clear_line() -> None:
    """Clears current line and positions cursor at start of line"""
    sys.stderr.write(f"\r{CLEAR_LINE}")
    sys.stdout.write(f"\r{CLEAR_LINE}")


================================================
FILE: src/pipx/colors.py
================================================
import sys
from typing import Callable

try:
    import colorama  # type: ignore[import-untyped]
except ImportError:  # Colorama is Windows only package
    colorama = None

PRINT_COLOR = bool(sys.stdout and sys.stdout.isatty())

if PRINT_COLOR and colorama:
    colorama.init()


class c:
    header = "\033[95m"
    blue = "\033[94m"
    green = "\033[92m"
    yellow = "\033[93m"
    red = "\033[91m"
    bold = "\033[1m"
    cyan = "\033[96m"
    underline = "\033[4m"
    end = "\033[0m"


def mkcolorfunc(style: str) -> Callable[[str], str]:
    def stylize_text(x: str) -> str:
        if PRINT_COLOR:
            return f"{style}{x}{c.end}"
        else:
            return x

    return stylize_text


bold = mkcolorfunc(c.bold)
red = mkcolorfunc(c.red)
blue = mkcolorfunc(c.cyan)
cyan = mkcolorfunc(c.blue)
green = mkcolorfunc(c.green)


================================================
FILE: src/pipx/commands/__init__.py
================================================
from pipx.commands.ensure_path import ensure_pipx_paths
from pipx.commands.environment import environment
from pipx.commands.inject import inject
from pipx.commands.install import install, install_all
from pipx.commands.interpreter import list_interpreters, prune_interpreters, upgrade_interpreters
from pipx.commands.list_packages import list_packages
from pipx.commands.pin import pin, unpin
from pipx.commands.reinstall import reinstall, reinstall_all
from pipx.commands.run import run
from pipx.commands.run_pip import run_pip
from pipx.commands.uninject import uninject
from pipx.commands.uninstall import uninstall, uninstall_all
from pipx.commands.upgrade import upgrade, upgrade_all, upgrade_shared

__all__ = [
    "ensure_pipx_paths",
    "environment",
    "inject",
    "install",
    "install_all",
    "list_interpreters",
    "list_packages",
    "pin",
    "prune_interpreters",
    "reinstall",
    "reinstall_all",
    "run",
    "run_pip",
    "uninject",
    "uninstall",
    "uninstall_all",
    "unpin",
    "upgrade",
    "upgrade_all",
    "upgrade_interpreters",
    "upgrade_shared",
]


================================================
FILE: src/pipx/commands/common.py
================================================
import filecmp
import logging
import os
import shlex
import shutil
import sys
import tempfile
import time
from pathlib import Path
from shutil import which
from tempfile import TemporaryDirectory
from typing import Optional

import userpath  # type: ignore[import-not-found]
from packaging.utils import canonicalize_name

from pipx import paths
from pipx.colors import bold, red
from pipx.constants import MAN_SECTIONS, WINDOWS
from pipx.emojis import hazard, stars
from pipx.package_specifier import parse_specifier_for_install, valid_pypi_name
from pipx.pipx_metadata_file import PackageInfo
from pipx.util import PipxError, mkdir, pipx_wrap, rmdir, safe_unlink
from pipx.venv import Venv

logger = logging.getLogger(__name__)


class VenvProblems:
    def __init__(
        self,
        bad_venv_name: bool = False,
        invalid_interpreter: bool = False,
        missing_metadata: bool = False,
        not_installed: bool = False,
    ) -> None:
        self.bad_venv_name = bad_venv_name
        self.invalid_interpreter = invalid_interpreter
        self.missing_metadata = missing_metadata
        self.not_installed = not_installed

    def any_(self) -> bool:
        return any(self.__dict__.values())

    def or_(self, venv_problems: "VenvProblems") -> None:
        for attribute in self.__dict__:
            setattr(
                self,
                attribute,
                getattr(self, attribute) or getattr(venv_problems, attribute),
            )


def expose_resources_globally(
    resource_type: str,
    local_resource_dir: Path,
    paths: list[Path],
    *,
    force: bool,
    suffix: str = "",
) -> None:
    for path in paths:
        src = path.resolve()
        if resource_type == "man":
            dest_dir = local_resource_dir / src.parent.name
        else:
            dest_dir = local_resource_dir
        if not dest_dir.is_dir():
            mkdir(dest_dir)
        if not can_symlink(dest_dir):
            _copy_package_resource(dest_dir, path, suffix=suffix)
        else:
            _symlink_package_resource(
                dest_dir,
                path,
                force=force,
                suffix=suffix,
                executable=(resource_type == "app"),
            )


_can_symlink_cache: dict[Path, bool] = {}


def can_symlink(local_resource_dir: Path) -> bool:
    if not WINDOWS:
        # Technically, even on Unix this depends on the filesystem
        return True

    if local_resource_dir not in _can_symlink_cache:
        with TemporaryDirectory(dir=local_resource_dir) as d:
            p = Path(d)
            target = p / "a"
            target.touch()
            lnk = p / "b"
            try:
                lnk.symlink_to(target)
                _can_symlink_cache[local_resource_dir] = True
            except (OSError, NotImplementedError):
                _can_symlink_cache[local_resource_dir] = False

    return _can_symlink_cache[local_resource_dir]


def _copy_package_resource(dest_dir: Path, path: Path, suffix: str = "") -> None:
    src = path.resolve()
    name = src.name
    dest = Path(dest_dir / add_suffix(name, suffix))
    if not dest.parent.is_dir():
        mkdir(dest.parent)
    if dest.exists():
        if filecmp.cmp(dest, src, shallow=False):
            return
        logger.warning(f"{hazard}  Overwriting file {dest!s} with {src!s}")
        safe_unlink(dest)
    if src.exists():
        shutil.copy(src, dest)


def _symlink_package_resource(
    dest_dir: Path,
    path: Path,
    *,
    force: bool,
    suffix: str = "",
    executable: bool = False,
) -> None:
    name_suffixed = add_suffix(path.name, suffix)
    symlink_path = Path(dest_dir / name_suffixed)

    if not symlink_path.parent.is_dir():
        mkdir(symlink_path.parent)

    if force:
        logger.info(f"Force is true. Removing {symlink_path!s}.")
        try:
            symlink_path.unlink()
        except (FileNotFoundError, RuntimeError):
            pass
        except IsADirectoryError:
            rmdir(symlink_path)

    exists = symlink_path.exists()
    is_symlink = symlink_path.is_symlink()
    if exists:
        if symlink_path.samefile(path):
            logger.info(f"Same path {symlink_path!s} and {path!s}")
        else:
            logger.warning(
                pipx_wrap(
                    f"""
                    {hazard}  File exists at {symlink_path!s} and points
                    to {symlink_path.resolve()}, not {path!s}. Not
                    modifying.
                    """,
                    subsequent_indent=" " * 4,
                )
            )
        return
    if is_symlink and not exists:
        logger.info(f"Removing existing symlink {symlink_path!s} since it pointed non-existent location")
        symlink_path.unlink()

    if executable:
        existing_executable_on_path = which(name_suffixed)
    else:
        existing_executable_on_path = None
    symlink_path.symlink_to(path)

    if executable and existing_executable_on_path:
        logger.warning(
            pipx_wrap(
                f"""
                {hazard}  Note: {name_suffixed} was already on your
                PATH at {existing_executable_on_path}
                """,
                subsequent_indent=" " * 4,
            )
        )


def venv_health_check(venv: Venv, package_name: Optional[str] = None) -> tuple[VenvProblems, str]:
    venv_dir = venv.root
    python_path = venv.python_path.resolve()

    if package_name is None:
        package_name = venv.main_package_name

    if not python_path.is_file():
        return (
            VenvProblems(invalid_interpreter=True),
            f"   package {red(bold(venv_dir.name))} has invalid interpreter {python_path!s}\r{hazard}",
        )
    if not venv.package_metadata:
        return (
            VenvProblems(missing_metadata=True),
            f"   package {red(bold(venv_dir.name))} has missing internal pipx metadata.\r{hazard}",
        )
    if venv_dir.name != canonicalize_name(venv_dir.name):
        return (
            VenvProblems(bad_venv_name=True),
            f"   package {red(bold(venv_dir.name))} needs its internal data updated.\r{hazard}",
        )
    if venv.package_metadata[package_name].package_version == "":
        return (
            VenvProblems(not_installed=True),
            f"   package {red(bold(package_name))} {red('is not installed')} in the venv {venv_dir.name}\r{hazard}",
        )
    return (VenvProblems(), "")


def get_venv_summary(
    venv_dir: Path,
    *,
    package_name: Optional[str] = None,
    new_install: bool = False,
    include_injected: bool = False,
) -> tuple[str, VenvProblems]:
    venv = Venv(venv_dir)

    if package_name is None:
        package_name = venv.main_package_name

    (venv_problems, warning_message) = venv_health_check(venv, package_name)
    if venv_problems.any_():
        return (warning_message, venv_problems)

    package_metadata = venv.package_metadata[package_name]
    apps = package_metadata.apps
    man_pages = package_metadata.man_pages
    if package_metadata.include_dependencies:
        apps += package_metadata.apps_of_dependencies
        man_pages += package_metadata.man_pages_of_dependencies

    exposed_app_paths = get_exposed_paths_for_package(
        venv.bin_path,
        paths.ctx.bin_dir,
        [add_suffix(app, package_metadata.suffix) for app in apps],
    )
    exposed_binary_names = sorted(p.name for p in exposed_app_paths)
    unavailable_binary_names = sorted(
        {add_suffix(name, package_metadata.suffix) for name in package_metadata.apps} - set(exposed_binary_names)
    )
    exposed_man_paths = set()
    for man_section in MAN_SECTIONS:
        exposed_man_paths |= get_exposed_man_paths_for_package(
            venv.man_path / man_section,
            paths.ctx.man_dir / man_section,
            man_pages,
        )
    exposed_man_pages = sorted(str(Path(p.parent.name) / p.name) for p in exposed_man_paths)
    unavailable_man_pages = sorted(set(package_metadata.man_pages) - set(exposed_man_pages))
    # The following is to satisfy mypy that python_version is str and not
    #   Optional[str]
    python_version = venv.pipx_metadata.python_version if venv.pipx_metadata.python_version is not None else ""
    source_interpreter = venv.pipx_metadata.source_interpreter
    is_standalone = (
        str(source_interpreter).startswith(str(paths.ctx.standalone_python_cachedir.resolve()))
        if source_interpreter
        else False
    )
    return (
        _get_list_output(
            python_version,
            is_standalone,
            package_metadata.package_version,
            package_name,
            new_install,
            exposed_binary_names,
            unavailable_binary_names,
            exposed_man_pages,
            unavailable_man_pages,
            venv.pipx_metadata.injected_packages if include_injected else None,
            suffix=package_metadata.suffix,
        ),
        venv_problems,
    )


def get_exposed_paths_for_package(
    venv_resource_path: Path,
    local_resource_dir: Path,
    package_resource_names: Optional[list[str]] = None,
) -> set[Path]:
    # package_binary_names is used only if local_bin_path cannot use symlinks.
    # It is necessary for non-symlink systems to return valid app_paths.
    if package_resource_names is None:
        package_resource_names = []

    if not local_resource_dir.exists():
        return set()

    symlinks = set()
    for b in local_resource_dir.iterdir():
        try:
            # sometimes symlinks can resolve to a file of a different name
            # (in the case of ansible for example) so checking the resolved paths
            # is not a reliable way to determine if the symlink exists.
            # We always use the stricter check on non-Windows systems. On
            # Windows, we use a less strict check if we don't have a symlink.
            is_same_file = False
            if can_symlink(local_resource_dir) and b.is_symlink():
                is_same_file = b.resolve().parent.samefile(venv_resource_path)
            elif not can_symlink(local_resource_dir):
                is_same_file = b.name in package_resource_names

            if is_same_file:
                symlinks.add(b)

        except (FileNotFoundError, RuntimeError):
            pass
    return symlinks


def get_exposed_man_paths_for_package(
    venv_man_path: Path,
    local_man_dir: Path,
    package_man_pages: Optional[list[str]] = None,
) -> set[Path]:
    man_section = venv_man_path.name
    prefix = man_section + os.sep
    return get_exposed_paths_for_package(
        venv_man_path,
        local_man_dir,
        [name.removeprefix(prefix) for name in package_man_pages or [] if name.startswith(prefix)],
    )


def _get_list_output(
    python_version: str,
    python_is_standalone: bool,
    package_version: str,
    package_name: str,
    new_install: bool,
    exposed_binary_names: list[str],
    unavailable_binary_names: list[str],
    exposed_man_pages: list[str],
    unavailable_man_pages: list[str],
    injected_packages: Optional[dict[str, PackageInfo]] = None,
    suffix: str = "",
) -> str:
    output = []
    suffix = f" ({bold(shlex.quote(package_name + suffix))})" if suffix else ""
    output.append(
        f"  {'installed' if new_install else ''} package {bold(shlex.quote(package_name))}"
        f" {bold(package_version)}{suffix}, installed using {python_version}"
        + (" (standalone)" if python_is_standalone else "")
    )

    if new_install and (exposed_binary_names or unavailable_binary_names):
        output.append("  These apps are now available")
    output.extend(f"    - {name}" for name in exposed_binary_names)
    output.extend(
        f"    - {red(name)} (symlink missing or pointing to unexpected location)" for name in unavailable_binary_names
    )
    if new_install and (exposed_man_pages or unavailable_man_pages):
        output.append("  These manual pages are now available")
    output.extend(f"    - {name}" for name in exposed_man_pages)
    output.extend(
        f"    - {red(name)} (symlink missing or pointing to unexpected location)" for name in unavailable_man_pages
    )
    if injected_packages:
        output.append("    Injected Packages:")
        output.extend(f"      - {name} {injected_packages[name].package_version}" for name in injected_packages)
    return "\n".join(output)


def package_name_from_spec(package_spec: str, python: str, *, pip_args: list[str], verbose: bool) -> str:
    start_time = time.time()

    # shortcut if valid PyPI name
    pypi_name = valid_pypi_name(package_spec)
    if pypi_name is not None:
        # NOTE: if pypi name and installed package name differ, this means pipx
        #       will use the pypi name
        package_name = pypi_name
        logger.info(f"Determined package name: {package_name}")
        logger.info(f"Package name determined in {time.time() - start_time:.1f}s")
        return package_name

    # check syntax and clean up spec and pip_args
    (package_spec, pip_args) = parse_specifier_for_install(package_spec, pip_args)

    with tempfile.TemporaryDirectory() as temp_venv_dir:
        venv = Venv(Path(temp_venv_dir), python=python, verbose=verbose)
        venv.create_venv(venv_args=[], pip_args=[])
        package_name = venv.install_package_no_deps(package_or_url=package_spec, pip_args=pip_args)

    logger.info(f"Package name determined in {time.time() - start_time:.1f}s")
    return package_name


def run_post_install_actions(
    venv: Venv,
    package_name: str,
    local_bin_dir: Path,
    local_man_dir: Path,
    venv_dir: Path,
    include_dependencies: bool,
    *,
    force: bool,
) -> None:
    package_metadata = venv.package_metadata[package_name]

    display_name = f"{package_name}{package_metadata.suffix}"

    if (
        not venv.main_package_name == package_name
        and venv.package_metadata[venv.main_package_name].suffix == package_metadata.suffix
    ):
        package_name = display_name

    if not package_metadata.apps:
        if not package_metadata.apps_of_dependencies:
            if venv.safe_to_remove():
                venv.remove_venv()
            raise PipxError(
                f"""
                No apps associated with package {display_name} or its
                dependencies. If you are attempting to install a library, pipx
                should not be used. Consider using pip or a similar tool instead.
                """
            )
        if package_metadata.apps_of_dependencies and not include_dependencies:
            for (
                dep,
                dependent_apps,
            ) in package_metadata.app_paths_of_dependencies.items():
                print(f"Note: Dependent package '{dep}' contains {len(dependent_apps)} apps")
                for app in dependent_apps:
                    print(f"  - {app.name}")
            if venv.safe_to_remove():
                venv.remove_venv()
            raise PipxError(
                f"""
                No apps associated with package {display_name}. Try again
                with '--include-deps' to include apps of dependent packages,
                which are listed above. If you are attempting to install a
                library, pipx should not be used. Consider using pip or a
                similar tool instead.
                """
            )

    expose_resources_globally(
        "app",
        local_bin_dir,
        package_metadata.app_paths,
        force=force,
        suffix=package_metadata.suffix,
    )
    expose_resources_globally("man", local_man_dir, package_metadata.man_paths, force=force)

    if include_dependencies:
        for app_paths in package_metadata.app_paths_of_dependencies.values():
            expose_resources_globally(
                "app",
                local_bin_dir,
                app_paths,
                force=force,
                suffix=package_metadata.suffix,
            )
        for man_paths in package_metadata.man_paths_of_dependencies.values():
            expose_resources_globally("man", local_man_dir, man_paths, force=force)

    package_summary, _ = get_venv_summary(venv_dir, package_name=package_name, new_install=True)
    pipx_logger = logging.getLogger("pipx")
    if not pipx_logger.handlers or pipx_logger.handlers[0].level <= logging.WARNING:
        print(package_summary)
        warn_if_not_on_path(local_bin_dir)
        print(f"done! {stars}", file=sys.stderr)


def warn_if_not_on_path(local_bin_dir: Path) -> None:
    if not userpath.in_current_path(str(local_bin_dir)):
        logger.warning(
            pipx_wrap(
                f"""
                {hazard}  Note: '{local_bin_dir}' is not on your PATH
                environment variable. These apps will not be globally
                accessible until your PATH is updated. Run `pipx ensurepath` to
                automatically add it, or manually modify your PATH in your
                shell's config file (e.g. ~/.bashrc).
                """,
                subsequent_indent=" " * 4,
            )
        )


def add_suffix(name: str, suffix: str) -> str:
    """Add suffix to app."""

    app = Path(name)
    return f"{app.stem}{suffix}{app.suffix}"


================================================
FILE: src/pipx/commands/ensure_path.py
================================================
import logging
import site
import sys
from pathlib import Path
from typing import Optional

import userpath  # type: ignore[import-not-found]

from pipx import paths
from pipx.constants import EXIT_CODE_OK, ExitCode
from pipx.emojis import hazard, stars
from pipx.util import pipx_wrap

logger = logging.getLogger(__name__)


def get_pipx_user_bin_path() -> Optional[Path]:
    """Returns None if pipx is not installed using `pip --user`
    Otherwise returns parent dir of pipx binary
    """
    # NOTE: using this method to detect pip user-installed pipx will return
    #   None if pipx was installed as editable using `pip install --
Download .txt
gitextract_awsmj108/

├── .deepsource.toml
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yaml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SECURITY.md
│   ├── config.yml
│   ├── dependabot.yaml
│   ├── release.yml
│   └── workflows/
│       ├── create_tests_package_lists.yml
│       ├── exhaustive_package_test.yml
│       ├── pre-release.yml
│       ├── release.yml
│       └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .pre-commit-hooks.yaml
├── .readthedocs.yml
├── LICENSE
├── changelog.d/
│   ├── .gitignore
│   ├── 1443.bugfix.md
│   ├── 1508.bugfix.md
│   ├── 1602.bugfix.md
│   └── 1718.bugfix.md
├── docs/
│   ├── README.md
│   ├── changelog.md
│   ├── contributing.md
│   ├── explanation/
│   │   ├── comparisons.md
│   │   ├── how-pipx-works.md
│   │   ├── index.md
│   │   └── making-packages-compatible.md
│   ├── how-to/
│   │   ├── configure-paths.md
│   │   ├── index.md
│   │   ├── inject-packages.md
│   │   ├── install-pipx.md
│   │   ├── move-installation.md
│   │   ├── pin-packages.md
│   │   ├── run-scripts.md
│   │   ├── shell-completions.md
│   │   ├── troubleshoot.md
│   │   ├── upgrade-pipx.md
│   │   └── use-with-pre-commit.md
│   ├── index.md
│   ├── reference/
│   │   ├── environment-variables.md
│   │   ├── examples.md
│   │   ├── index.md
│   │   └── programs-to-try.md
│   └── tutorial/
│       ├── getting-started.md
│       ├── index.md
│       ├── install-applications.md
│       └── run-applications.md
├── get-pipx.py
├── mkdocs.yml
├── pyproject.toml
├── scripts/
│   ├── gen_doc_pages.py
│   ├── generate_man.py
│   ├── list_test_packages.py
│   ├── migrate_pipsi_to_pipx.py
│   ├── release.py
│   ├── templates/
│   │   └── docs.md
│   ├── test_packages_support.py
│   └── update_package_cache.py
├── src/
│   └── pipx/
│       ├── __init__.py
│       ├── __main__.py
│       ├── animate.py
│       ├── colors.py
│       ├── commands/
│       │   ├── __init__.py
│       │   ├── common.py
│       │   ├── ensure_path.py
│       │   ├── environment.py
│       │   ├── inject.py
│       │   ├── install.py
│       │   ├── interpreter.py
│       │   ├── list_packages.py
│       │   ├── pin.py
│       │   ├── reinstall.py
│       │   ├── run.py
│       │   ├── run_pip.py
│       │   ├── uninject.py
│       │   ├── uninstall.py
│       │   └── upgrade.py
│       ├── constants.py
│       ├── emojis.py
│       ├── interpreter.py
│       ├── main.py
│       ├── package_specifier.py
│       ├── paths.py
│       ├── pipx_metadata_file.py
│       ├── shared_libs.py
│       ├── standalone_python.py
│       ├── util.py
│       ├── venv.py
│       ├── venv_inspect.py
│       └── version.pyi
├── testdata/
│   ├── empty_project/
│   │   ├── README.md
│   │   ├── empty_project/
│   │   │   ├── __init__.py
│   │   │   └── main.py
│   │   └── pyproject.toml
│   ├── pipx_metadata_multiple_errors.json
│   ├── standalone_python_index_20250818.json
│   ├── standalone_python_index_20250828.json
│   ├── test_package_specifier/
│   │   └── local_extras/
│   │       ├── repeatme/
│   │       │   ├── __init__.py
│   │       │   └── main.py
│   │       └── setup.py
│   └── tests_packages/
│       └── README.md
├── tests/
│   ├── conftest.py
│   ├── helpers.py
│   ├── package_info.py
│   ├── test_animate.py
│   ├── test_common.py
│   ├── test_completions.py
│   ├── test_emojis.py
│   ├── test_environment.py
│   ├── test_inject.py
│   ├── test_install.py
│   ├── test_install_all.py
│   ├── test_install_all_packages.py
│   ├── test_interpreter.py
│   ├── test_list.py
│   ├── test_main.py
│   ├── test_package_specifier.py
│   ├── test_pin.py
│   ├── test_pipx_metadata_file.py
│   ├── test_reinstall.py
│   ├── test_reinstall_all.py
│   ├── test_run.py
│   ├── test_runpip.py
│   ├── test_shared_libs.py
│   ├── test_standalone_interpreter.py
│   ├── test_uninject.py
│   ├── test_uninstall.py
│   ├── test_uninstall_all.py
│   ├── test_unpin.py
│   ├── test_upgrade.py
│   ├── test_upgrade_all.py
│   └── test_upgrade_shared.py
└── tox.toml
Download .txt
SYMBOL INDEX (550 symbols across 69 files)

FILE: get-pipx.py
  function fail (line 5) | def fail(msg):
  function main (line 11) | def main():

FILE: scripts/gen_doc_pages.py
  function get_help (line 11) | def get_help(cmd: Optional[str]) -> str:

FILE: scripts/generate_man.py
  function main (line 13) | def main():

FILE: scripts/list_test_packages.py
  function process_command_line (line 14) | def process_command_line(argv: list[str]) -> argparse.Namespace:
  function parse_package_list (line 46) | def parse_package_list(package_list_file: Path) -> list[dict[str, Any]]:
  function create_test_packages_list (line 68) | def create_test_packages_list(package_list_dir_path: Path, primary_packa...
  function download (line 107) | def download(test_package: dict[str, str], verbose: bool) -> set[str]:
  function main (line 127) | def main(argv: list[str]) -> int:

FILE: scripts/migrate_pipsi_to_pipx.py
  function main (line 14) | def main():

FILE: scripts/release.py
  function main (line 15) | def main(version_str: str, *, push: bool) -> None:
  function resolve_version (line 34) | def resolve_version(version_str: str, repo: Repo) -> Version:
  function get_remote (line 52) | def get_remote(repo: Repo) -> Remote:
  function release_changelog (line 64) | def release_changelog(repo: Repo, version: Version) -> Commit:
  function tag_release_commit (line 73) | def tag_release_commit(release_commit: Commit, repo: Repo, version: Vers...

FILE: scripts/test_packages_support.py
  function get_platform_list_path (line 16) | def get_platform_list_path(package_list_dir_path: Path) -> Path:
  function get_platform_packages_dir_path (line 20) | def get_platform_packages_dir_path(pipx_package_cache_path: Path) -> Path:

FILE: scripts/update_package_cache.py
  function process_command_line (line 13) | def process_command_line(argv: list[str]) -> argparse.Namespace:
  function update_test_packages_cache (line 54) | def update_test_packages_cache(package_list_dir_path: Path, pipx_package...
  function download (line 153) | def download(package_spec: str, packages_dir_path: Path) -> int:
  function main (line 179) | def main(argv: list[str]) -> int:

FILE: src/pipx/animate.py
  class _CursorInfo (line 26) | class _CursorInfo(ctypes.Structure):
  function _env_supports_animation (line 30) | def _env_supports_animation() -> bool:
  function animate (line 36) | def animate(message: str, do_animation: bool, *, delay: float = 0) -> Ge...
  function print_animation (line 78) | def print_animation(
  function win_cursor (line 109) | def win_cursor(visible: bool) -> None:
  function hide_cursor (line 119) | def hide_cursor() -> None:
  function show_cursor (line 128) | def show_cursor() -> None:
  function clear_line (line 137) | def clear_line() -> None:

FILE: src/pipx/colors.py
  class c (line 15) | class c:
  function mkcolorfunc (line 27) | def mkcolorfunc(style: str) -> Callable[[str], str]:

FILE: src/pipx/commands/common.py
  class VenvProblems (line 29) | class VenvProblems:
    method __init__ (line 30) | def __init__(
    method any_ (line 42) | def any_(self) -> bool:
    method or_ (line 45) | def or_(self, venv_problems: "VenvProblems") -> None:
  function expose_resources_globally (line 54) | def expose_resources_globally(
  function can_symlink (line 85) | def can_symlink(local_resource_dir: Path) -> bool:
  function _copy_package_resource (line 105) | def _copy_package_resource(dest_dir: Path, path: Path, suffix: str = "")...
  function _symlink_package_resource (line 120) | def _symlink_package_resource(
  function venv_health_check (line 182) | def venv_health_check(venv: Venv, package_name: Optional[str] = None) ->...
  function get_venv_summary (line 212) | def get_venv_summary(
  function get_exposed_paths_for_package (line 280) | def get_exposed_paths_for_package(
  function get_exposed_man_paths_for_package (line 315) | def get_exposed_man_paths_for_package(
  function _get_list_output (line 329) | def _get_list_output(
  function package_name_from_spec (line 368) | def package_name_from_spec(package_spec: str, python: str, *, pip_args: ...
  function run_post_install_actions (line 393) | def run_post_install_actions(
  function warn_if_not_on_path (line 473) | def warn_if_not_on_path(local_bin_dir: Path) -> None:
  function add_suffix (line 489) | def add_suffix(name: str, suffix: str) -> str:

FILE: src/pipx/commands/ensure_path.py
  function get_pipx_user_bin_path (line 17) | def get_pipx_user_bin_path() -> Optional[Path]:
  function ensure_path (line 54) | def ensure_path(location: Path, *, force: bool, prepend: bool = False, a...
  function ensure_pipx_paths (line 103) | def ensure_pipx_paths(force: bool, prepend: bool = False, all_shells: bo...

FILE: src/pipx/commands/environment.py
  function environment (line 32) | def environment(value: str) -> ExitCode:

FILE: src/pipx/commands/inject.py
  function inject_dep (line 22) | def inject_dep(
  function inject (line 112) | def inject(
  function parse_requirements (line 158) | def parse_requirements(filename: Union[str, os.PathLike]) -> Generator[s...

FILE: src/pipx/commands/install.py
  function install (line 21) | def install(
  function extract_venv_metadata (line 130) | def extract_venv_metadata(spec_metadata_file: Path) -> Iterator[PipxMeta...
  function generate_package_spec (line 153) | def generate_package_spec(package_info: PackageInfo) -> str:
  function get_python_interpreter (line 163) | def get_python_interpreter(
  function install_all (line 182) | def install_all(

FILE: src/pipx/commands/interpreter.py
  function get_installed_standalone_interpreters (line 16) | def get_installed_standalone_interpreters() -> list[Path]:
  function get_venvs_using_standalone_interpreter (line 20) | def get_venvs_using_standalone_interpreter(venv_container: VenvContainer...
  function get_interpreter_users (line 29) | def get_interpreter_users(interpreter: Path, venvs: list[Venv]) -> list[...
  function list_interpreters (line 38) | def list_interpreters(
  function prune_interpreters (line 58) | def prune_interpreters(
  function get_latest_micro_version (line 78) | def get_latest_micro_version(
  function upgrade_interpreters (line 87) | def upgrade_interpreters(venv_container: VenvContainer, verbose: bool):

FILE: src/pipx/commands/list_packages.py
  function get_venv_metadata_summary (line 21) | def get_venv_metadata_summary(venv_dir: Path) -> tuple[PipxMetadata, Ven...
  function list_short (line 31) | def list_short(venv_dirs: Collection[Path]) -> VenvProblems:
  function list_text (line 47) | def list_text(venv_dirs: Collection[Path], include_injected: bool, venv_...
  function list_json (line 64) | def list_json(venv_dirs: Collection[Path]) -> VenvProblems:
  function list_pinned (line 88) | def list_pinned(venv_dirs: Collection[Path], include_injected: bool) -> ...
  function list_packages (line 109) | def list_packages(

FILE: src/pipx/commands/pin.py
  function _update_pin_info (line 14) | def _update_pin_info(venv: Venv, package_name: str, is_main_package: boo...
  function pin (line 29) | def pin(
  function unpin (line 71) | def unpin(venv_dir: Path, verbose: bool) -> ExitCode:

FILE: src/pipx/commands/reinstall.py
  function reinstall (line 21) | def reinstall(
  function reinstall_all (line 105) | def reinstall_all(

FILE: src/pipx/commands/run.py
  function maybe_script_content (line 46) | def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str,...
  function run_script (line 78) | def run_script(
  function run_package (line 123) | def run_package(
  function run (line 201) | def run(
  function _prepare_venv (line 245) | def _prepare_venv(
  function _get_temporary_venv_path (line 306) | def _get_temporary_venv_path(requirements: list[str], python: str, pip_a...
  function _is_temporary_venv_expired (line 321) | def _is_temporary_venv_expired(venv_dir: Path) -> bool:
  function _prepare_venv_cache (line 329) | def _prepare_venv_cache(venv: Venv, bin_path: Optional[Path], use_cache:...
  function _remove_all_expired_venvs (line 337) | def _remove_all_expired_venvs() -> None:
  function _http_get_request (line 344) | def _http_get_request(url: str) -> str:
  function _get_requirements_from_script (line 358) | def _get_requirements_from_script(content: Union[str, Path]) -> Optional...

FILE: src/pipx/commands/run_pip.py
  function run_pip (line 8) | def run_pip(package: str, venv_dir: Path, pip_args: list[str], verbose: ...

FILE: src/pipx/commands/uninject.py
  function get_include_resource_paths (line 25) | def get_include_resource_paths(package_name: str, venv: Venv, local_bin_...
  function uninject_dep (line 50) | def uninject_dep(
  function uninject (line 116) | def uninject(

FILE: src/pipx/commands/uninstall.py
  function _venv_metadata_to_package_info (line 31) | def _venv_metadata_to_package_info(
  function _get_package_bin_dir_app_paths (line 62) | def _get_package_bin_dir_app_paths(
  function _get_package_man_paths (line 74) | def _get_package_man_paths(venv: Venv, package_info: PackageInfo, venv_m...
  function _get_venv_resource_paths (line 83) | def _get_venv_resource_paths(
  function uninstall (line 124) | def uninstall(venv_dir: Path, local_bin_dir: Path, local_man_dir: Path, ...
  function uninstall_all (line 157) | def uninstall_all(

FILE: src/pipx/commands/upgrade.py
  function _upgrade_package (line 20) | def _upgrade_package(
  function _upgrade_venv (line 108) | def _upgrade_venv(
  function upgrade (line 207) | def upgrade(
  function upgrade_all (line 239) | def upgrade_all(
  function upgrade_shared (line 281) | def upgrade_shared(

FILE: src/pipx/constants.py
  function is_windows (line 28) | def is_windows() -> bool:
  function is_macos (line 32) | def is_macos() -> bool:
  function is_linux (line 36) | def is_linux() -> bool:
  function is_mingw (line 40) | def is_mingw() -> bool:

FILE: src/pipx/emojis.py
  function strtobool (line 5) | def strtobool(val: str) -> bool:
  function use_emojis (line 15) | def use_emojis() -> bool:

FILE: src/pipx/interpreter.py
  function has_venv (line 18) | def has_venv() -> bool:
  class InterpreterResolutionError (line 26) | class InterpreterResolutionError(PipxError):
    method __init__ (line 27) | def __init__(self, source: str, version: str, wrap_message: bool = True):
  function find_unix_command_python (line 53) | def find_unix_command_python(python_version: str) -> Optional[str]:
  function find_python_interpreter (line 86) | def find_python_interpreter(python_version: str, fetch_missing_python: b...
  function find_py_launcher_python (line 122) | def find_py_launcher_python(python_version: Optional[str] = None) -> Opt...
  function _find_default_windows_python (line 140) | def _find_default_windows_python() -> str:
  function _get_sys_executable (line 165) | def _get_sys_executable() -> str:
  function _get_absolute_python_interpreter (line 172) | def _get_absolute_python_interpreter(env_python: str) -> str:

FILE: src/pipx/main.py
  function print_version (line 49) | def print_version() -> None:
  function prog_name (line 53) | def prog_name() -> str:
  class LineWrapRawTextHelpFormatter (line 156) | class LineWrapRawTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
    method _split_lines (line 157) | def _split_lines(self, text: str, width: int) -> list[str]:
  class InstalledVenvsCompleter (line 162) | class InstalledVenvsCompleter:
    method __init__ (line 163) | def __init__(self, venv_container: VenvContainer) -> None:
    method use (line 166) | def use(self, prefix: str, **kwargs: Any) -> list[str]:
  function get_pip_args (line 170) | def get_pip_args(parsed_args: dict[str, str]) -> list[str]:
  function get_runpip_args (line 187) | def get_runpip_args(pip_args: list[str]) -> list[str]:
  function get_venv_args (line 200) | def get_venv_args(parsed_args: dict[str, str]) -> list[str]:
  function package_is_url (line 207) | def package_is_url(package: str, raise_error: bool = True) -> bool:
  function package_is_path (line 216) | def package_is_path(package: str):
  function run_pipx_command (line 228) | def run_pipx_command(args: argparse.Namespace, subparsers: dict[str, arg...
  function add_pip_venv_args (line 447) | def add_pip_venv_args(parser: argparse.ArgumentParser) -> None:
  function add_include_dependencies (line 466) | def add_include_dependencies(parser: argparse.ArgumentParser) -> None:
  function add_python_options (line 470) | def add_python_options(parser: argparse.ArgumentParser) -> None:
  function _add_install (line 488) | def _add_install(subparsers: argparse._SubParsersAction, shared_parser: ...
  function _add_install_all (line 524) | def _add_install_all(subparsers: argparse._SubParsersAction, shared_pars...
  function _add_inject (line 543) | def _add_inject(subparsers, venv_completer: VenvCompleter, shared_parser...
  function _add_uninject (line 596) | def _add_uninject(subparsers, venv_completer: VenvCompleter, shared_pars...
  function _add_pin (line 619) | def _add_pin(subparsers, venv_completer: VenvCompleter, shared_parser: a...
  function _add_unpin (line 643) | def _add_unpin(subparsers, venv_completer: VenvCompleter, shared_parser:...
  function _add_upgrade (line 653) | def _add_upgrade(subparsers, venv_completer: VenvCompleter, shared_parse...
  function _add_upgrade_all (line 681) | def _add_upgrade_all(subparsers: argparse._SubParsersAction, shared_pars...
  function _add_upgrade_shared (line 702) | def _add_upgrade_shared(subparsers: argparse._SubParsersAction, shared_p...
  function _add_uninstall (line 715) | def _add_uninstall(subparsers, venv_completer: VenvCompleter, shared_par...
  function _add_uninstall_all (line 725) | def _add_uninstall_all(subparsers: argparse._SubParsersAction, shared_pa...
  function _add_reinstall (line 734) | def _add_reinstall(subparsers, venv_completer: VenvCompleter, shared_par...
  function _add_reinstall_all (line 754) | def _add_reinstall_all(subparsers: argparse._SubParsersAction, shared_pa...
  function _add_list (line 776) | def _add_list(subparsers: argparse._SubParsersAction, shared_parser: arg...
  function _add_interpreter (line 799) | def _add_interpreter(
  function _add_run (line 823) | def _add_run(subparsers: argparse._SubParsersAction, shared_parser: argp...
  function _add_runpip (line 884) | def _add_runpip(subparsers, venv_completer: VenvCompleter, shared_parser...
  function _add_ensurepath (line 903) | def _add_ensurepath(subparsers: argparse._SubParsersAction, shared_parse...
  function _add_environment (line 940) | def _add_environment(subparsers: argparse._SubParsersAction, shared_pars...
  function get_command_parser (line 966) | def get_command_parser() -> tuple[argparse.ArgumentParser, dict[str, arg...
  function delete_oldest_logs (line 1065) | def delete_oldest_logs(file_list: list[Path], keep_number: int) -> None:
  function _setup_log_file (line 1075) | def _setup_log_file(pipx_log_dir: Optional[Path] = None) -> Path:
  function setup_log_file (line 1096) | def setup_log_file() -> Path:
  function setup_logging (line 1103) | def setup_logging(verbose: int) -> None:
  function setup (line 1152) | def setup(args: argparse.Namespace) -> None:
  function check_args (line 1208) | def check_args(parsed_pipx_args: argparse.Namespace) -> None:
  function cli (line 1220) | def cli() -> ExitCode:

FILE: src/pipx/package_specifier.py
  class ParsedPackage (line 29) | class ParsedPackage:
  function _split_path_extras (line 35) | def _split_path_extras(package_spec: str) -> tuple[str, str]:
  function _check_package_path (line 44) | def _check_package_path(package_path: str) -> tuple[Path, bool]:
  function _parse_specifier (line 51) | def _parse_specifier(package_spec: str) -> ParsedPackage:
  function package_or_url_from_pep508 (line 108) | def package_or_url_from_pep508(requirement: Requirement, remove_version_...
  function _parsed_package_to_package_or_url (line 116) | def _parsed_package_to_package_or_url(parsed_package: ParsedPackage, rem...
  function parse_specifier_for_install (line 143) | def parse_specifier_for_install(package_spec: str, pip_args: list[str]) ...
  function parse_specifier_for_metadata (line 189) | def parse_specifier_for_metadata(package_spec: str) -> str:
  function parse_specifier_for_upgrade (line 200) | def parse_specifier_for_upgrade(package_spec: str) -> str:
  function get_extras (line 212) | def get_extras(package_spec: str) -> set[str]:
  function valid_pypi_name (line 223) | def valid_pypi_name(package_spec: str) -> Optional[str]:
  function fix_package_name (line 239) | def fix_package_name(package_or_url: str, package_name: str) -> str:

FILE: src/pipx/paths.py
  function get_expanded_environ (line 40) | def get_expanded_environ(env_name: str) -> Optional[Path]:
  class _PathContext (line 47) | class _PathContext:
    method __init__ (line 62) | def __init__(self):
    method venvs (line 66) | def venvs(self) -> Path:
    method logs (line 70) | def logs(self) -> Path:
    method trash (line 76) | def trash(self) -> Path:
    method venv_cache (line 82) | def venv_cache(self) -> Path:
    method bin_dir (line 88) | def bin_dir(self) -> Path:
    method man_dir (line 92) | def man_dir(self) -> Path:
    method home (line 96) | def home(self) -> Path:
    method shared_libs (line 106) | def shared_libs(self) -> Path:
    method make_local (line 109) | def make_local(self) -> None:
    method make_global (line 123) | def make_global(self) -> None:
    method standalone_python_cachedir (line 138) | def standalone_python_cachedir(self) -> Path:
    method allow_spaces_in_home_path (line 142) | def allow_spaces_in_home_path(self) -> bool:
    method log_warnings (line 145) | def log_warnings(self):

FILE: src/pipx/pipx_metadata_file.py
  class JsonEncoderHandlesPath (line 16) | class JsonEncoderHandlesPath(json.JSONEncoder):
    method default (line 17) | def default(self, obj: Any) -> Any:
  function _json_decoder_object_hook (line 24) | def _json_decoder_object_hook(json_dict: dict[str, Any]) -> Union[dict[s...
  class PackageInfo (line 31) | class PackageInfo:
  class PipxMetadata (line 50) | class PipxMetadata:
    method __init__ (line 59) | def __init__(self, venv_dir: Path, read: bool = True):
    method to_dict (line 90) | def to_dict(self) -> dict[str, Any]:
    method _convert_legacy_metadata (line 100) | def _convert_legacy_metadata(self, metadata_dict: dict[str, Any]) -> d...
    method from_dict (line 123) | def from_dict(self, input_dict: dict[str, Any]) -> None:
    method _validate_before_write (line 136) | def _validate_before_write(self) -> None:
    method write (line 145) | def write(self) -> None:
    method read (line 169) | def read(self, verbose: bool = False) -> None:

FILE: src/pipx/shared_libs.py
  function _venv_python_is_valid (line 28) | def _venv_python_is_valid(python_path: Path) -> bool:
  class _SharedLibs (line 62) | class _SharedLibs:
    method __init__ (line 63) | def __init__(self) -> None:
    method root (line 69) | def root(self) -> Path:
    method bin_path (line 73) | def bin_path(self) -> Path:
    method python_path (line 78) | def python_path(self) -> Path:
    method man_path (line 83) | def man_path(self) -> Path:
    method pip_path (line 88) | def pip_path(self) -> Path:
    method site_packages (line 92) | def site_packages(self) -> Path:
    method create (line 98) | def create(self, pip_args: list[str], verbose: bool = False) -> None:
    method is_valid (line 113) | def is_valid(self) -> bool:
    method needs_upgrade (line 131) | def needs_upgrade(self) -> bool:
    method upgrade (line 148) | def upgrade(self, *, pip_args: list[str], verbose: bool = False, raise...

FILE: src/pipx/standalone_python.py
  function download_python_build_standalone (line 50) | def download_python_build_standalone(python_version: str, override: bool...
  function _download (line 95) | def _download(full_version: str, download_link: str, archive: Path):
  function _unpack (line 107) | def _unpack(full_version, download_link, archive: Path, download_dir: Pa...
  function get_or_update_index (line 128) | def get_or_update_index(use_cache: bool = True):
  function get_latest_python_releases (line 148) | def get_latest_python_releases() -> list[tuple[str, str]]:
  function list_pythons (line 161) | def list_pythons(use_cache: bool = True) -> dict[str, tuple[str, str]]:
  function resolve_python_version (line 203) | def resolve_python_version(requested_version: str):

FILE: src/pipx/util.py
  class PipxError (line 28) | class PipxError(Exception):
    method __init__ (line 29) | def __init__(self, message: str, wrap_message: bool = True):
  class RelevantSearch (line 37) | class RelevantSearch:
  function _get_trash_file (line 42) | def _get_trash_file(path: Path) -> Path:
  function rmdir (line 49) | def rmdir(path: Path, safe_rm: bool = True) -> None:
  function mkdir (line 70) | def mkdir(path: Path) -> None:
  function safe_unlink (line 77) | def safe_unlink(file: Path) -> None:
  function get_pypackage_bin_path (line 90) | def get_pypackage_bin_path(binary_name: str) -> Path:
  function run_pypackage_bin (line 100) | def run_pypackage_bin(bin_path: Path, args: list[str]) -> NoReturn:
  function get_venv_paths (line 109) | def get_venv_paths(root: Path) -> tuple[Path, Path, Path]:
  function get_venv_paths (line 121) | def get_venv_paths(root: Path) -> tuple[Path, Path, Path]:
  function get_site_packages (line 128) | def get_site_packages(python: Path) -> Path:
  function _fix_subprocess_env (line 138) | def _fix_subprocess_env(env: dict[str, str]) -> dict[str, str]:
  function run_subprocess (line 157) | def run_subprocess(
  function subprocess_post_check (line 200) | def subprocess_post_check(completed_process: "subprocess.CompletedProces...
  function dedup_ordered (line 212) | def dedup_ordered(input_list: list[tuple[str, Any]]) -> list[tuple[str, ...
  function analyze_pip_output (line 223) | def analyze_pip_output(pip_stdout: str, pip_stderr: str) -> None:
  function subprocess_post_check_handle_pip_error (line 325) | def subprocess_post_check_handle_pip_error(
  function exec_app (line 349) | def exec_app(
  function full_package_description (line 390) | def full_package_description(package_name: str, package_spec: str) -> str:
  function pipx_wrap (line 397) | def pipx_wrap(text: str, subsequent_indent: str = "", keep_newlines: boo...
  function is_paths_relative (line 424) | def is_paths_relative(path: Path, parent: Path):

FILE: src/pipx/venv.py
  class VenvContainer (line 58) | class VenvContainer:
    method __init__ (line 61) | def __init__(self, root: Path):
    method __repr__ (line 64) | def __repr__(self) -> str:
    method __str__ (line 67) | def __str__(self) -> str:
    method iter_venv_dirs (line 70) | def iter_venv_dirs(self) -> Generator[Path, None, None]:
    method get_venv_dir (line 79) | def get_venv_dir(self, package_name: str) -> Path:
  class Venv (line 84) | class Venv:
    method __init__ (line 87) | def __init__(self, path: Path, *, verbose: bool = False, python: str =...
    method check_upgrade_shared_libs (line 99) | def check_upgrade_shared_libs(self, verbose: bool, pip_args: list[str]...
    method name (line 129) | def name(self) -> str:
    method uses_shared_libs (line 137) | def uses_shared_libs(self) -> bool:
    method package_metadata (line 146) | def package_metadata(self) -> dict[str, PackageInfo]:
    method main_package_name (line 153) | def main_package_name(self) -> str:
    method create_venv (line 161) | def create_venv(self, venv_args: list[str], pip_args: list[str], overr...
    method safe_to_remove (line 193) | def safe_to_remove(self) -> bool:
    method remove_venv (line 196) | def remove_venv(self) -> None:
    method upgrade_packaging_libraries (line 210) | def upgrade_packaging_libraries(self, pip_args: list[str]) -> None:
    method uninstall_package (line 218) | def uninstall_package(self, package: str, was_injected: bool = False):
    method install_package (line 232) | def install_package(
    method install_unmanaged_packages (line 288) | def install_unmanaged_packages(self, requirements: list[str], pip_args...
    method install_package_no_deps (line 313) | def install_package_no_deps(self, package_or_url: str, pip_args: list[...
    method get_venv_metadata_for_package (line 349) | def get_venv_metadata_for_package(self, package_name: str, package_ext...
    method update_package_metadata (line 355) | def update_package_metadata(
    method get_python_version (line 392) | def get_python_version(self) -> str:
    method list_installed_packages (line 395) | def list_installed_packages(self, not_required=False) -> set[str]:
    method _find_entry_point (line 402) | def _find_entry_point(self, app: str) -> Optional[EntryPoint]:
    method run_app (line 417) | def run_app(self, app: str, filename: str, app_args: list[str]) -> NoR...
    method has_app (line 434) | def has_app(self, app: str, filename: str) -> bool:
    method has_package (line 439) | def has_package(self, package_name: str) -> bool:
    method upgrade_package_no_metadata (line 442) | def upgrade_package_no_metadata(self, package_name: str, pip_args: lis...
    method upgrade_package (line 448) | def upgrade_package(
    method _run_pip (line 473) | def _run_pip(self, cmd: list[str]) -> "CompletedProcess[str]":
    method run_pip_get_exit_code (line 479) | def run_pip_get_exit_code(self, cmd: list[str]) -> ExitCode:

FILE: src/pipx/venv_inspect.py
  class VenvInspectInformation (line 22) | class VenvInspectInformation(NamedTuple):
  class VenvMetadata (line 29) | class VenvMetadata(NamedTuple):
  function get_dist (line 42) | def get_dist(package: str, distributions: Collection[metadata.Distributi...
  function get_package_dependencies (line 50) | def get_package_dependencies(dist: metadata.Distribution, extras: set[st...
  function get_apps_from_entry_points (line 69) | def get_apps_from_entry_points(dist: metadata.Distribution, bin_path: Pa...
  function get_resources_from_dist_files (line 84) | def get_resources_from_dist_files(dist: metadata.Distribution, bin_path:...
  function get_resources_from_inst_files (line 106) | def get_resources_from_inst_files(dist: metadata.Distribution, bin_path:...
  function get_resources (line 124) | def get_resources(dist: metadata.Distribution, bin_path: Path, man_path:...
  function _dfs_package_resources (line 135) | def _dfs_package_resources(
  function _windows_extra_app_paths (line 175) | def _windows_extra_app_paths(app_paths: list[Path]) -> list[Path]:
  function fetch_info_in_venv (line 191) | def fetch_info_in_venv(venv_python_path: Path) -> tuple[list[str], dict[...
  function inspect_venv (line 251) | def inspect_venv(

FILE: testdata/empty_project/empty_project/main.py
  function main (line 1) | def main():

FILE: testdata/test_package_specifier/local_extras/repeatme/main.py
  function main (line 11) | def main():

FILE: tests/conftest.py
  function root (line 24) | def root() -> Path:
  function mocked_github_api (line 29) | def mocked_github_api(monkeypatch, root):
  function pytest_addoption (line 39) | def pytest_addoption(parser):
  function pytest_configure (line 56) | def pytest_configure(config):
  function pipx_temp_env_helper (line 67) | def pipx_temp_env_helper(pipx_shared_dir, tmp_path, monkeypatch, request...
  function pipx_local_pypiserver (line 117) | def pipx_local_pypiserver(request, root: Path, tmp_path_factory) -> Iter...
  function pipx_session_shared_dir (line 184) | def pipx_session_shared_dir(tmp_path_factory):
  function utils_temp_dir (line 190) | def utils_temp_dir(tmp_path_factory):
  function pipx_temp_env (line 205) | def pipx_temp_env(tmp_path, monkeypatch, pipx_session_shared_dir, reques...
  function pipx_ultra_temp_env (line 220) | def pipx_ultra_temp_env(tmp_path, monkeypatch, request, utils_temp_dir, ...

FILE: tests/helpers.py
  function app_name (line 76) | def app_name(app: str) -> str:
  function run_pipx_cli (line 80) | def run_pipx_cli(pipx_args: list[str]) -> int:
  function unwrap_log_text (line 85) | def unwrap_log_text(log_text: str):
  function _mock_legacy_package_info (line 95) | def _mock_legacy_package_info(modern_package_info: dict[str, Any], metad...
  function mock_legacy_venv (line 110) | def mock_legacy_venv(venv_name: str, metadata_version: Optional[str] = N...
  function create_package_info_ref (line 161) | def create_package_info_ref(venv_name, package_name, pipx_venvs_dir, **f...
  function assert_package_metadata (line 192) | def assert_package_metadata(test_metadata, ref_metadata):
  function remove_venv_interpreter (line 216) | def remove_venv_interpreter(venv_name):

FILE: tests/package_info.py
  function _exe_if_win (line 8) | def _exe_if_win(apps):

FILE: tests/test_animate.py
  function check_animate_output (line 19) | def check_animate_output(
  function test_delay_suppresses_output (line 73) | def test_delay_suppresses_output(capsys, monkeypatch):
  function test_line_lengths_emoji (line 94) | def test_line_lengths_emoji(capsys, monkeypatch, env_columns, expected_f...
  function test_line_lengths_no_emoji (line 113) | def test_line_lengths_no_emoji(capsys, monkeypatch, env_columns, expecte...
  function test_env_no_animate (line 132) | def test_env_no_animate(capsys, monkeypatch, env_columns, stderr_is_tty):

FILE: tests/test_common.py
  function test_get_exposed_paths_ignores_recursive_symlink (line 6) | def test_get_exposed_paths_ignores_recursive_symlink(tmp_path):

FILE: tests/test_completions.py
  function test_cli (line 4) | def test_cli(monkeypatch, capsys):

FILE: tests/test_emojis.py
  function test_use_emojis (line 42) | def test_use_emojis(monkeypatch, PIPX_USE_EMOJI, encoding, expected):

FILE: tests/test_environment.py
  function test_cli (line 12) | def test_cli(pipx_temp_env, monkeypatch, capsys):
  function test_cli_with_args (line 28) | def test_cli_with_args(monkeypatch, capsys):
  function test_resolve_user_dir_in_env_paths (line 48) | def test_resolve_user_dir_in_env_paths(monkeypatch):
  function test_allow_space_in_pipx_home (line 58) | def test_allow_space_in_pipx_home(
  function test_cli_global (line 82) | def test_cli_global(pipx_temp_env, monkeypatch, capsys):

FILE: tests/test_inject.py
  function test_inject_single_package (line 22) | def test_inject_single_package(pipx_temp_env, capsys, caplog, pkg_spec):
  function test_inject_simple_global (line 37) | def test_inject_simple_global(pipx_temp_env, capsys):
  function test_inject_simple_legacy_venv (line 43) | def test_inject_simple_legacy_venv(pipx_temp_env, capsys, metadata_versi...
  function test_inject_include_apps (line 55) | def test_inject_include_apps(pipx_temp_env, capsys, with_suffix):
  function test_inject_with_req_file (line 80) | def test_inject_with_req_file(pipx_temp_env, capsys, caplog, tmp_path, w...

FILE: tests/test_install.py
  function test_help_text (line 17) | def test_help_text(monkeypatch, capsys):
  function install_packages (line 25) | def install_packages(capsys, pipx_temp_env, caplog, packages, package_na...
  function test_install_easy_packages (line 47) | def test_install_easy_packages(capsys, pipx_temp_env, caplog, package_na...
  function test_install_easy_multiple_packages (line 51) | def test_install_easy_multiple_packages(capsys, pipx_temp_env, caplog):
  function test_install_easy_packages_globally (line 66) | def test_install_easy_packages_globally(capsys, pipx_temp_env, caplog, p...
  function test_install_tricky_packages (line 79) | def test_install_tricky_packages(capsys, pipx_temp_env, caplog, package_...
  function test_install_multiple_packages_when_some_already_installed (line 88) | def test_install_multiple_packages_when_some_already_installed(capsys, p...
  function test_install_tricky_multiple_packages (line 101) | def test_install_tricky_multiple_packages(capsys, pipx_temp_env, caplog):
  function test_install_package_specs (line 119) | def test_install_package_specs(capsys, pipx_temp_env, caplog, package_na...
  function test_force_install (line 123) | def test_force_install(pipx_temp_env, capsys):
  function test_install_no_packages_found (line 139) | def test_install_no_packages_found(pipx_temp_env, capsys):
  function test_install_same_package_twice_no_force (line 145) | def test_install_same_package_twice_no_force(pipx_temp_env, capsys):
  function test_include_deps (line 152) | def test_include_deps(pipx_temp_env, capsys):
  function test_name_tricky_characters (line 164) | def test_name_tricky_characters(caplog, capsys, pipx_temp_env, package_n...
  function test_extra (line 171) | def test_extra(pipx_temp_env, capsys):
  function test_install_local_extra (line 177) | def test_install_local_extra(pipx_temp_env, capsys, monkeypatch, root):
  function test_path_warning (line 184) | def test_path_warning(pipx_temp_env, capsys, monkeypatch, caplog):
  function test_existing_symlink_points_to_existing_wrong_location_warning (line 194) | def test_existing_symlink_points_to_existing_wrong_location_warning(pipx...
  function test_existing_man_page_symlink_points_to_existing_wrong_location_warning (line 207) | def test_existing_man_page_symlink_points_to_existing_wrong_location_war...
  function test_existing_symlink_points_to_nothing (line 217) | def test_existing_symlink_points_to_nothing(pipx_temp_env, capsys):
  function test_existing_man_page_symlink_points_to_nothing (line 228) | def test_existing_man_page_symlink_points_to_nothing(pipx_temp_env, caps...
  function test_pip_args_forwarded_to_shared_libs (line 238) | def test_pip_args_forwarded_to_shared_libs(pipx_ultra_temp_env, capsys, ...
  function test_pip_args_forwarded_to_package_name_determination (line 253) | def test_pip_args_forwarded_to_package_name_determination(pipx_temp_env,...
  function test_pip_args_with_windows_path (line 267) | def test_pip_args_with_windows_path(pipx_temp_env, capsys):
  function test_pip_args_with_constraint_relative_path (line 284) | def test_pip_args_with_constraint_relative_path(constraint_flag, pipx_te...
  function test_pip_args_with_wrong_constraint_fail (line 304) | def test_pip_args_with_wrong_constraint_fail(constraint_flag, pipx_ultra...
  function test_install_suffix (line 316) | def test_install_suffix(pipx_temp_env, capsys):
  function test_man_page_install (line 335) | def test_man_page_install(pipx_temp_env, capsys):
  function test_install_pip_failure (line 342) | def test_install_pip_failure(pipx_temp_env, capsys):
  function test_install_local_archive (line 355) | def test_install_local_archive(pipx_temp_env, monkeypatch, capsys, root):
  function test_force_install_changes (line 364) | def test_force_install_changes(pipx_temp_env, capsys):
  function test_force_install_changes_editable (line 374) | def test_force_install_changes_editable(pipx_temp_env, root, capsys):
  function test_preinstall (line 385) | def test_preinstall(pipx_temp_env, caplog):
  function test_preinstall_multiple (line 390) | def test_preinstall_multiple(pipx_temp_env, caplog):
  function test_preinstall_specific_version (line 396) | def test_preinstall_specific_version(pipx_temp_env, caplog):
  function test_do_not_wait_for_input (line 402) | def test_do_not_wait_for_input(pipx_temp_env, pipx_session_shared_dir, m...
  function test_passed_python_and_force_flag_warning (line 407) | def test_passed_python_and_force_flag_warning(pipx_temp_env, capsys):
  function test_install_fetch_missing_python_invalid (line 430) | def test_install_fetch_missing_python_invalid(capsys, python_version):
  function test_install_run_in_separate_directory (line 436) | def test_install_run_in_separate_directory(caplog, capsys, pipx_temp_env...
  function test_install_python_command_version (line 453) | def test_install_python_command_version(pipx_temp_env, monkeypatch, caps...
  function test_install_python_command_version_invalid (line 461) | def test_install_python_command_version_invalid(pipx_temp_env, capsys):
  function test_install_python_command_version_unsupported (line 469) | def test_install_python_command_version_unsupported(pipx_temp_env, capsys):
  function test_install_python_command_version_missing (line 477) | def test_install_python_command_version_missing(pipx_temp_env, monkeypat...
  function test_install_python_command_version_micro_mismatch (line 486) | def test_install_python_command_version_micro_mismatch(pipx_temp_env, mo...
  function test_global_flag_before_subcommand_rejected (line 495) | def test_global_flag_before_subcommand_rejected(pipx_temp_env, capsys):
  function test_install_quiet_flag (line 503) | def test_install_quiet_flag(pipx_temp_env, capsys):

FILE: tests/test_install_all.py
  function test_install_all (line 7) | def test_install_all(pipx_temp_env, tmp_path, capsys):
  function test_install_all_multiple_errors (line 26) | def test_install_all_multiple_errors(pipx_temp_env, root, capsys):

FILE: tests/test_install_all_packages.py
  class PackageData (line 100) | class PackageData:
    method __init__ (line 101) | def __init__(self):
    method clear_pip_pf_str (line 113) | def clear_pip_pf_str(self) -> str:
    method clear_pipx_pf_str (line 117) | def clear_pipx_pf_str(self) -> str:
    method sys_pip_pf_str (line 121) | def sys_pip_pf_str(self) -> str:
    method sys_pipx_pf_str (line 125) | def sys_pipx_pf_str(self) -> str:
    method overall_pf_str (line 129) | def overall_pf_str(self) -> str:
    method _get_pass_fail_str (line 132) | def _get_pass_fail_str(self, test_attr: str) -> str:
  class ModuleGlobalsData (line 139) | class ModuleGlobalsData:
    method __init__ (line 140) | def __init__(self):
    method reset (line 152) | def reset(self, test_class: str = "") -> None:
  function module_globals (line 161) | def module_globals() -> ModuleGlobalsData:
  function pip_cache_purge (line 165) | def pip_cache_purge() -> None:
  function write_report_legend (line 169) | def write_report_legend(report_legend_path: Path) -> None:
  function format_report_table_header (line 192) | def format_report_table_header(module_globals: ModuleGlobalsData) -> str:
  function format_report_table_row (line 211) | def format_report_table_row(package_data: PackageData) -> str:
  function format_report_table_footer (line 227) | def format_report_table_footer(module_globals: ModuleGlobalsData) -> str:
  function verify_installed_resources (line 263) | def verify_installed_resources(
  function verify_post_install (line 305) | def verify_post_install(
  function print_error_report (line 350) | def print_error_report(
  function install_and_verify (line 381) | def install_and_verify(
  function install_package_both_paths (line 427) | def install_package_both_paths(
  function start_end_test_class (line 488) | def start_end_test_class(module_globals: ModuleGlobalsData, request):
  class TestAllPackagesNoDeps (line 525) | class TestAllPackagesNoDeps:
    method test_all_packages (line 530) | def test_all_packages(
  class TestAllPackagesDeps (line 552) | class TestAllPackagesDeps:
    method test_deps_all_packages (line 557) | def test_deps_all_packages(

FILE: tests/test_interpreter.py
  function test_windows_python_with_version (line 25) | def test_windows_python_with_version(monkeypatch, venv):
  function test_windows_python_with_python_and_version (line 43) | def test_windows_python_with_python_and_version(monkeypatch, venv):
  function test_windows_python_with_python_and_unavailable_version (line 61) | def test_windows_python_with_python_and_unavailable_version(monkeypatch,...
  function test_windows_python_no_version_with_venv (line 76) | def test_windows_python_no_version_with_venv(monkeypatch):
  function test_windows_python_no_version_no_venv_with_py (line 81) | def test_windows_python_no_version_no_venv_with_py(monkeypatch):
  function test_windows_python_no_version_no_venv_python_present (line 90) | def test_windows_python_no_version_no_venv_python_present(monkeypatch):
  function test_windows_python_no_version_no_venv_no_python (line 101) | def test_windows_python_no_version_no_venv_no_python(monkeypatch):
  function test_windows_python_no_venv_store_python (line 112) | def test_windows_python_no_venv_store_python(monkeypatch):
  function test_bad_env_python (line 146) | def test_bad_env_python(monkeypatch):
  function test_good_env_python (line 151) | def test_good_env_python(monkeypatch, capsys):
  function test_find_python_interpreter_by_path (line 156) | def test_find_python_interpreter_by_path(monkeypatch):
  function test_find_python_interpreter_by_version (line 161) | def test_find_python_interpreter_by_version(monkeypatch):
  function test_find_python_interpreter_by_wrong_path_raises (line 168) | def test_find_python_interpreter_by_wrong_path_raises(monkeypatch):
  function test_find_python_interpreter_missing_on_path_raises (line 175) | def test_find_python_interpreter_missing_on_path_raises(monkeypatch):
  function test_fetch_missing_python (line 183) | def test_fetch_missing_python(monkeypatch, mocked_github_api):

FILE: tests/test_list.py
  function test_cli (line 25) | def test_cli(pipx_temp_env, monkeypatch, capsys):
  function test_cli_global (line 32) | def test_cli_global(pipx_temp_env, monkeypatch, capsys):
  function test_missing_interpreter (line 42) | def test_missing_interpreter(pipx_temp_env, monkeypatch, capsys):
  function test_list_suffix (line 56) | def test_list_suffix(pipx_temp_env, monkeypatch, capsys):
  function test_list_legacy_venv (line 66) | def test_list_legacy_venv(pipx_temp_env, monkeypatch, capsys, metadata_v...
  function test_list_suffix_legacy_venv (line 81) | def test_list_suffix_legacy_venv(pipx_temp_env, monkeypatch, capsys, met...
  function test_list_json (line 91) | def test_list_json(pipx_temp_env, capsys):
  function test_list_short (line 146) | def test_list_short(pipx_temp_env, monkeypatch, capsys):
  function test_list_standalone_interpreter (line 158) | def test_list_standalone_interpreter(pipx_temp_env, monkeypatch, mocked_...
  function test_list_does_not_trigger_maintenance (line 185) | def test_list_does_not_trigger_maintenance(pipx_temp_env, caplog):
  function test_list_pinned_packages (line 216) | def test_list_pinned_packages(pipx_temp_env, monkeypatch, capsys):
  function test_list_pinned_packages_include_injected (line 229) | def test_list_pinned_packages_include_injected(pipx_temp_env, monkeypatc...

FILE: tests/test_main.py
  function test_help_text (line 10) | def test_help_text(monkeypatch, capsys):
  function test_version (line 18) | def test_version(monkeypatch, capsys):
  function test_prog_name (line 34) | def test_prog_name(monkeypatch, argv, executable, expected):
  function test_limit_verbosity (line 40) | def test_limit_verbosity():

FILE: tests/test_package_specifier.py
  function test_valid_pypi_name (line 27) | def test_valid_pypi_name(package_spec_in, package_name_out):
  function test_fix_package_name (line 51) | def test_fix_package_name(package_spec_in, package_name, package_spec_out):
  function test_parse_specifier_for_metadata (line 114) | def test_parse_specifier_for_metadata(package_spec_in, package_or_url_co...
  function test_parse_specifier_for_upgrade (line 182) | def test_parse_specifier_for_upgrade(package_spec_in, package_or_url_cor...
  function test_parse_specifier_for_install (line 264) | def test_parse_specifier_for_install(

FILE: tests/test_pin.py
  function test_pin (line 5) | def test_pin(capsys, pipx_temp_env, caplog):
  function test_pin_with_suffix (line 13) | def test_pin_with_suffix(capsys, pipx_temp_env, caplog):
  function test_pin_warning (line 21) | def test_pin_warning(capsys, pipx_temp_env, caplog):
  function test_pin_not_installed_package (line 29) | def test_pin_not_installed_package(capsys, pipx_temp_env):
  function test_pin_injected_packages_only (line 36) | def test_pin_injected_packages_only(capsys, pipx_temp_env, caplog):
  function test_pin_injected_packages_with_skip (line 54) | def test_pin_injected_packages_with_skip(capsys, pipx_temp_env):

FILE: tests/test_pipx_metadata_file.py
  function test_pipx_metadata_file_create (line 45) | def test_pipx_metadata_file_create(tmp_path):
  function test_pipx_metadata_file_validation (line 77) | def test_pipx_metadata_file_validation(tmp_path, test_package):
  function test_package_install (line 92) | def test_package_install(monkeypatch, tmp_path, pipx_temp_env):
  function test_package_inject (line 104) | def test_package_inject(monkeypatch, tmp_path, pipx_temp_env):

FILE: tests/test_reinstall.py
  function test_reinstall (line 8) | def test_reinstall(pipx_temp_env, capsys):
  function test_reinstall_global (line 14) | def test_reinstall_global(pipx_temp_env, capsys):
  function test_reinstall_nonexistent (line 19) | def test_reinstall_nonexistent(pipx_temp_env, capsys):
  function test_reinstall_legacy_venv (line 25) | def test_reinstall_legacy_venv(pipx_temp_env, capsys, metadata_version):
  function test_reinstall_suffix (line 32) | def test_reinstall_suffix(pipx_temp_env, capsys):
  function test_reinstall_suffix_legacy_venv (line 40) | def test_reinstall_suffix_legacy_venv(pipx_temp_env, capsys, metadata_ve...
  function test_reinstall_specifier (line 48) | def test_reinstall_specifier(pipx_temp_env, capsys):
  function test_reinstall_with_path (line 59) | def test_reinstall_with_path(pipx_temp_env, capsys, tmp_path):
  function test_reinstall_pinned_package (line 73) | def test_reinstall_pinned_package(pipx_temp_env, capsys):

FILE: tests/test_reinstall_all.py
  function test_reinstall_all (line 9) | def test_reinstall_all(pipx_temp_env, capsys):
  function test_reinstall_all_none (line 14) | def test_reinstall_all_none(pipx_temp_env, capsys):
  function test_reinstall_all_legacy_venv (line 21) | def test_reinstall_all_legacy_venv(pipx_temp_env, capsys, metadata_versi...
  function test_reinstall_all_suffix (line 28) | def test_reinstall_all_suffix(pipx_temp_env, capsys):
  function test_reinstall_all_suffix_legacy_venv (line 36) | def test_reinstall_all_suffix_legacy_venv(pipx_temp_env, capsys, metadat...
  function test_reinstall_all_triggers_shared_libs_upgrade (line 44) | def test_reinstall_all_triggers_shared_libs_upgrade(pipx_temp_env, caplo...

FILE: tests/test_run.py
  function test_help_text (line 18) | def test_help_text(pipx_temp_env, monkeypatch, capsys):
  function execvpe_mock (line 26) | def execvpe_mock(cmd_path, cmd_args, env):
  function run_pipx_cli_exit (line 38) | def run_pipx_cli_exit(pipx_cmd_list, assert_exit=None):
  function test_simple_run (line 48) | def test_simple_run(pipx_temp_env, monkeypatch, capsys, package_name):
  function test_cache (line 55) | def test_cache(pipx_temp_env, monkeypatch, capsys, caplog):
  function test_cachedir_tag (line 66) | def test_cachedir_tag(pipx_ultra_temp_env, monkeypatch, capsys, caplog):
  function test_run_script_from_internet (line 88) | def test_run_script_from_internet(pipx_temp_env, capsys):
  function test_appargs_doubledash (line 118) | def test_appargs_doubledash(pipx_temp_env, capsys, monkeypatch, input_ru...
  function test_run_ensure_null_pythonpath (line 126) | def test_run_ensure_null_pythonpath():
  function test_package_determination (line 165) | def test_package_determination(caplog, pipx_temp_env, package, package_o...
  function test_run_without_requirements (line 179) | def test_run_without_requirements(caplog, pipx_temp_env, tmp_path):
  function test_run_with_requirements (line 239) | def test_run_with_requirements(script_text, expected_output, caplog, pip...
  function test_run_with_requirements_old (line 251) | def test_run_with_requirements_old(caplog, pipx_temp_env, tmp_path):
  function test_run_correct_traceback (line 277) | def test_run_correct_traceback(capfd, pipx_temp_env, tmp_path):
  function test_run_with_args (line 295) | def test_run_with_args(caplog, pipx_temp_env, tmp_path):
  function test_run_with_requirements_and_args (line 312) | def test_run_with_requirements_and_args(caplog, pipx_temp_env, tmp_path):
  function test_run_with_failing_requirements (line 333) | def test_run_with_failing_requirements(capfd, pipx_temp_env, tmp_path):
  function test_pip_args_forwarded_to_shared_libs (line 366) | def test_pip_args_forwarded_to_shared_libs(pipx_ultra_temp_env, capsys, ...
  function test_run_with_invalid_requirement (line 382) | def test_run_with_invalid_requirement(capsys, pipx_temp_env, tmp_path):
  function test_run_script_by_absolute_name (line 402) | def test_run_script_by_absolute_name(caplog, pipx_temp_env, tmp_path):
  function test_run_script_by_relative_name (line 419) | def test_run_script_by_relative_name(caplog, pipx_temp_env, monkeypatch,...
  function test_run_script_by_file_descriptor (line 439) | def test_run_script_by_file_descriptor(caplog, pipx_temp_env, monkeypatc...
  function test_run_with_windows_python_version (line 468) | def test_run_with_windows_python_version(caplog, pipx_temp_env, tmp_path):
  function test_run_verify_script_name_provided (line 485) | def test_run_verify_script_name_provided(pipx_temp_env, capsys, tmpdir):
  function test_run_shared_lib_as_app (line 493) | def test_run_shared_lib_as_app(pipx_temp_env, monkeypatch, capfd):
  function test_run_local_path_entry_point (line 500) | def test_run_local_path_entry_point(pipx_temp_env, caplog, root):
  function test_run_with (line 512) | def test_run_with(capsys):
  function test_run_with_cache (line 519) | def test_run_with_cache(capsys, caplog):

FILE: tests/test_runpip.py
  function test_runpip (line 4) | def test_runpip(pipx_temp_env, monkeypatch, capsys):
  function test_runpip_splits_single_argument (line 9) | def test_runpip_splits_single_argument(pipx_temp_env, monkeypatch, capsys):
  function test_runpip_global (line 15) | def test_runpip_global(pipx_temp_env, monkeypatch, capsys):

FILE: tests/test_shared_libs.py
  function test_auto_update_shared_libs (line 19) | def test_auto_update_shared_libs(capsys, pipx_ultra_temp_env, mtime_minu...
  function test_venv_python_is_valid_missing_interpreter (line 31) | def test_venv_python_is_valid_missing_interpreter(tmp_path: Path) -> None:
  function test_venv_python_is_valid_existing_interpreter (line 48) | def test_venv_python_is_valid_existing_interpreter(tmp_path: Path) -> None:
  function test_venv_python_is_valid_non_windows (line 70) | def test_venv_python_is_valid_non_windows() -> None:

FILE: tests/test_standalone_interpreter.py
  function mock_which (line 18) | def mock_which(name):
  function test_list_no_standalone_interpreters (line 24) | def test_list_no_standalone_interpreters(pipx_temp_env, monkeypatch, cap...
  function test_list_used_standalone_interpreters (line 32) | def test_list_used_standalone_interpreters(pipx_temp_env, monkeypatch, m...
  function test_list_unused_standalone_interpreters (line 53) | def test_list_unused_standalone_interpreters(pipx_temp_env, monkeypatch,...
  function test_prune_unused_standalone_interpreters (line 76) | def test_prune_unused_standalone_interpreters(pipx_temp_env, monkeypatch...
  function test_upgrade_standalone_interpreter (line 112) | def test_upgrade_standalone_interpreter(pipx_temp_env, root, monkeypatch...
  function test_upgrade_standalone_interpreter_nothing_to_upgrade (line 136) | def test_upgrade_standalone_interpreter_nothing_to_upgrade(pipx_temp_env...

FILE: tests/test_uninject.py
  function test_uninject_simple (line 5) | def test_uninject_simple(pipx_temp_env, capsys):
  function test_uninject_simple_global (line 17) | def test_uninject_simple_global(pipx_temp_env, capsys):
  function test_uninject_with_include_apps (line 28) | def test_uninject_with_include_apps(pipx_temp_env, capsys, caplog):
  function test_uninject_leave_deps (line 35) | def test_uninject_leave_deps(pipx_temp_env, capsys, caplog):

FILE: tests/test_uninstall.py
  function file_or_symlink (line 17) | def file_or_symlink(filepath):
  function test_uninstall (line 27) | def test_uninstall(pipx_temp_env):
  function test_uninstall_global (line 33) | def test_uninstall_global(pipx_temp_env):
  function test_uninstall_legacy_venv (line 45) | def test_uninstall_legacy_venv(pipx_temp_env, metadata_version):
  function test_uninstall_suffix (line 56) | def test_uninstall_suffix(pipx_temp_env):
  function test_uninstall_man_page (line 68) | def test_uninstall_man_page(pipx_temp_env):
  function test_uninstall_injected (line 76) | def test_uninstall_injected(pipx_temp_env):
  function test_uninstall_suffix_legacy_venv (line 102) | def test_uninstall_suffix_legacy_venv(pipx_temp_env, metadata_version):
  function test_uninstall_with_missing_interpreter (line 118) | def test_uninstall_with_missing_interpreter(pipx_temp_env, metadata_vers...
  function test_uninstall_proper_dep_behavior (line 134) | def test_uninstall_proper_dep_behavior(pipx_temp_env, metadata_version):
  function test_uninstall_rejects_existing_directory_name (line 158) | def test_uninstall_rejects_existing_directory_name(pipx_temp_env, tmp_pa...
  function test_uninstall_proper_dep_behavior_missing_interpreter (line 175) | def test_uninstall_proper_dep_behavior_missing_interpreter(pipx_temp_env...

FILE: tests/test_uninstall_all.py
  function test_uninstall_all (line 6) | def test_uninstall_all(pipx_temp_env, capsys):
  function test_uninstall_all_legacy_venv (line 12) | def test_uninstall_all_legacy_venv(pipx_temp_env, capsys, metadata_versi...

FILE: tests/test_unpin.py
  function test_unpin (line 5) | def test_unpin(capsys, pipx_temp_env, caplog):
  function test_unpin_with_suffix (line 16) | def test_unpin_with_suffix(capsys, pipx_temp_env):
  function test_unpin_warning (line 30) | def test_unpin_warning(capsys, pipx_temp_env, caplog):
  function test_unpin_not_installed_package (line 39) | def test_unpin_not_installed_package(capsys, pipx_temp_env):
  function test_unpin_injected_packages (line 46) | def test_unpin_injected_packages(capsys, pipx_temp_env):

FILE: tests/test_upgrade.py
  function test_upgrade (line 13) | def test_upgrade(pipx_temp_env, capsys):
  function test_upgrade_global (line 28) | def test_upgrade_global(pipx_temp_env, capsys):
  function test_upgrade_legacy_venv (line 43) | def test_upgrade_legacy_venv(pipx_temp_env, capsys, metadata_version):
  function test_upgrade_suffix (line 56) | def test_upgrade_suffix(pipx_temp_env, capsys):
  function test_upgrade_suffix_legacy_venv (line 66) | def test_upgrade_suffix_legacy_venv(pipx_temp_env, capsys, metadata_vers...
  function test_upgrade_specifier (line 76) | def test_upgrade_specifier(pipx_temp_env, capsys):
  function test_upgrade_missing_interpreter (line 87) | def test_upgrade_missing_interpreter(pipx_temp_env, capsys):
  function test_upgrade_editable (line 98) | def test_upgrade_editable(pipx_temp_env, capsys, root):
  function test_upgrade_include_injected (line 106) | def test_upgrade_include_injected(pipx_temp_env, capsys):
  function test_upgrade_no_include_injected (line 116) | def test_upgrade_no_include_injected(pipx_temp_env, capsys):
  function test_upgrade_install_missing (line 126) | def test_upgrade_install_missing(pipx_temp_env, capsys):
  function test_upgrade_multiple (line 132) | def test_upgrade_multiple(pipx_temp_env, capsys):
  function test_upgrade_absolute_path (line 146) | def test_upgrade_absolute_path(pipx_temp_env, capsys, root):
  function test_upgrade_with_extras (line 152) | def test_upgrade_with_extras(pipx_temp_env, capsys):

FILE: tests/test_upgrade_all.py
  function test_upgrade_all (line 6) | def test_upgrade_all(pipx_temp_env, capsys):
  function test_upgrade_all_none (line 12) | def test_upgrade_all_none(pipx_temp_env, capsys):
  function test_upgrade_all_legacy_venv (line 20) | def test_upgrade_all_legacy_venv(pipx_temp_env, capsys, caplog, metadata...

FILE: tests/test_upgrade_shared.py
  function shared_libs (line 9) | def shared_libs(pipx_ultra_temp_env):
  function test_upgrade_shared (line 16) | def test_upgrade_shared(shared_libs, capsys, caplog):
  function test_upgrade_shared_pip_args (line 39) | def test_upgrade_shared_pip_args(shared_libs, capsys, caplog):
  function test_upgrade_shared_pin_pip (line 52) | def test_upgrade_shared_pin_pip(shared_libs):
Condensed preview — 139 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (949K chars).
[
  {
    "path": ".deepsource.toml",
    "chars": 184,
    "preview": "version = 1\n\ntest_patterns = [\"tests/**\"]\n\n[[analyzers]]\nname = \"python\"\nenabled = true\n\n  [analyzers.meta]\n  runtime_ve"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 517,
    "preview": "# Contributing to `pipx`\n\nThank you for your interest in contributing to pipx! There are many ways to contribute, and we"
  },
  {
    "path": ".github/FUNDING.yaml",
    "chars": 22,
    "preview": "tidelift: \"pypi/pipx\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.md",
    "chars": 369,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n\n**Issue**\n\nDescr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 670,
    "preview": "# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#con"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 744,
    "preview": "---\nname: Feature request\nabout: Suggest an enhancement for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n---"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 428,
    "preview": "### Thanks for contributing, make sure you address all the checklists (for details on how see [development documentation"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 365,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 1.7.0+  | "
  },
  {
    "path": ".github/config.yml",
    "chars": 21,
    "preview": "rtd:\n  project: pipx\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "chars": 117,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/release.yml",
    "chars": 114,
    "preview": "changelog:\n  exclude:\n    authors:\n      - dependabot[bot]\n      - pre-commit-ci[bot]\n      - github-actions[bot]\n"
  },
  {
    "path": ".github/workflows/create_tests_package_lists.yml",
    "chars": 1176,
    "preview": "name: Create tests package lists for offline tests\non:\n  workflow_dispatch:\nconcurrency:\n  group: create-tests-package-l"
  },
  {
    "path": ".github/workflows/exhaustive_package_test.yml",
    "chars": 2327,
    "preview": "name: Exhaustive Package Test (slow)\non:\n  workflow_dispatch:\nconcurrency:\n  group: exhaustive-package-test-${{ github.r"
  },
  {
    "path": ".github/workflows/pre-release.yml",
    "chars": 1201,
    "preview": "name: Pre-release\non:\n  workflow_dispatch:\n    inputs:\n      bump:\n        description: \"Version bump type\"\n        requ"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1993,
    "preview": "name: Release\non:\n  push:\n    tags:\n      - \"*.*.*\"\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  can"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 2818,
    "preview": "name: 🧪 tests\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - \"main\"\n  pull_request:\n  schedule:\n    - cron: \"0 8"
  },
  {
    "path": ".gitignore",
    "chars": 212,
    "preview": "/.*_cache\n/build\n/dist\n/src/pipx/version.py\n/noxfile.py\n/.nox\n*.py[co]\n__pycache__\n/site\n/.coverage*\n/.pipx_tests\n/testd"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1755,
    "preview": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n      - id: end-of-file-fixer"
  },
  {
    "path": ".pre-commit-hooks.yaml",
    "chars": 122,
    "preview": "- id: pipx\n  name: pipx\n  entry: pipx run\n  require_serial: true\n  language: python\n  minimum_pre_commit_version: '2.9.2"
  },
  {
    "path": ".readthedocs.yml",
    "chars": 136,
    "preview": "version: 2\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.13\"\n  commands:\n    - pip install \"tox>=4.45\" tox-uv\n    - "
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "MIT License\n\nCopyright (c) 2018 Chad Smith and contributors\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "changelog.d/.gitignore",
    "chars": 12,
    "preview": "!.gitignore\n"
  },
  {
    "path": "changelog.d/1443.bugfix.md",
    "chars": 77,
    "preview": "Fix `--global` flag being silently ignored when placed before the subcommand\n"
  },
  {
    "path": "changelog.d/1508.bugfix.md",
    "chars": 81,
    "preview": "Respect `--quiet` flag in `install` and suppress animations and success messages\n"
  },
  {
    "path": "changelog.d/1602.bugfix.md",
    "chars": 89,
    "preview": "Catch missing Python interpreter in `upgrade` and show helpful error instead of crashing\n"
  },
  {
    "path": "changelog.d/1718.bugfix.md",
    "chars": 108,
    "preview": "Prevent data loss by rejecting relative paths in `uninstall`, `reinstall`, and other package-name arguments\n"
  },
  {
    "path": "docs/README.md",
    "chars": 2902,
    "preview": "<p align=\"center\">\n<a href=\"https://pipx.pypa.io\">\n<img align=\"center\" src=\"https://github.com/pypa/pipx/raw/main/logo.s"
  },
  {
    "path": "docs/changelog.md",
    "chars": 40830,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "docs/contributing.md",
    "chars": 10848,
    "preview": "---\nhide:\n  - navigation\n---\n\nThanks for your interest in contributing to pipx!\n\nEveryone who interacts with the pipx pr"
  },
  {
    "path": "docs/explanation/comparisons.md",
    "chars": 6822,
    "preview": "## pipx vs pip\n\n- pip is a general Python package installer. It can be used to install libraries or CLI applications wit"
  },
  {
    "path": "docs/explanation/how-pipx-works.md",
    "chars": 3541,
    "preview": "## How it Works\n\n### `pipx install`\n\nWhen installing a package and its binaries on Linux (`pipx install package`), pipx "
  },
  {
    "path": "docs/explanation/index.md",
    "chars": 438,
    "preview": "# Explanation\n\nDesign rationale and background knowledge. Read these to understand *why* pipx works the way it does.\n\n- "
  },
  {
    "path": "docs/explanation/making-packages-compatible.md",
    "chars": 4479,
    "preview": "## Developing for pipx\n\nIf you are a developer and want to be able to run\n\n```\npipx install MY_PACKAGE\n```\n\nmake sure yo"
  },
  {
    "path": "docs/how-to/configure-paths.md",
    "chars": 5030,
    "preview": "## Installation Options\n\nThe default binary location for pipx-installed apps is `~/.local/bin`. This can be overridden w"
  },
  {
    "path": "docs/how-to/index.md",
    "chars": 947,
    "preview": "# How-to Guides\n\nRecipes for common pipx tasks. Each guide covers a specific goal and assumes you have pipx installed.\n\n"
  },
  {
    "path": "docs/how-to/inject-packages.md",
    "chars": 1198,
    "preview": "## Inject a package\n\n`pipx inject` adds extra packages into an existing pipx-managed virtual environment. If you have `i"
  },
  {
    "path": "docs/how-to/install-pipx.md",
    "chars": 6200,
    "preview": "## System Requirements\n\npython 3.9+ is required to install pipx. pipx can run binaries from packages with Python 3.3+. D"
  },
  {
    "path": "docs/how-to/move-installation.md",
    "chars": 2039,
    "preview": "## Moving your pipx installation\n\nThe below code snippets show how to move your pipx installation to a new directory. As"
  },
  {
    "path": "docs/how-to/pin-packages.md",
    "chars": 1139,
    "preview": "## Pin installed packages\n\nUse `pipx pin` when you need to hold an installation at its current version. Pinned packages "
  },
  {
    "path": "docs/how-to/run-scripts.md",
    "chars": 1947,
    "preview": "## Running a specific version of a package\n\nThe `PACKAGE` argument is a\n[requirement specifier](https://packaging.python"
  },
  {
    "path": "docs/how-to/shell-completions.md",
    "chars": 156,
    "preview": "## Shell Completion\n\nYou can easily get your shell's tab completions working by following instructions printed with this"
  },
  {
    "path": "docs/how-to/troubleshoot.md",
    "chars": 8312,
    "preview": "## Wrong package version installed\n\npipx creates venvs using your default Python interpreter. pip resolves the latest pa"
  },
  {
    "path": "docs/how-to/upgrade-pipx.md",
    "chars": 1059,
    "preview": "## Upgrade pipx\n\nOn macOS:\n\n```\nbrew update && brew upgrade pipx\n```\n\nOn Ubuntu Linux:\n\n```\nsudo apt upgrade pipx\n```\n\nO"
  },
  {
    "path": "docs/how-to/use-with-pre-commit.md",
    "chars": 656,
    "preview": "## Using pipx with pre-commit\n\npipx has [pre-commit](https://pre-commit.com/) support. This lets you run applications:\n\n"
  },
  {
    "path": "docs/index.md",
    "chars": 4444,
    "preview": "<p align=\"center\">\n<a href=\"https://pipx.pypa.io\">\n<img align=\"center\" src=\"https://github.com/pypa/pipx/raw/main/logo.s"
  },
  {
    "path": "docs/reference/environment-variables.md",
    "chars": 2889,
    "preview": "## Environment Variables\n\npipx reads the following environment variables. All are optional.\n\n| Variable                 "
  },
  {
    "path": "docs/reference/examples.md",
    "chars": 7444,
    "preview": "## `pipx install` examples\n\n```\npipx install pycowsay\npipx install --python python3.10 pycowsay\npipx install --python 3."
  },
  {
    "path": "docs/reference/index.md",
    "chars": 466,
    "preview": "# Reference\n\nTechnical reference for pipx: CLI flags, environment variables, and runnable examples.\n\n- [CLI Reference](c"
  },
  {
    "path": "docs/reference/programs-to-try.md",
    "chars": 2037,
    "preview": "## Programs\n\nHere are some programs you can try out. If you've never used the program before, make sure you add the `--h"
  },
  {
    "path": "docs/tutorial/getting-started.md",
    "chars": 1476,
    "preview": "## Getting Started with pipx\n\nThis tutorial covers the core pipx workflow: installing an application, running it, and ma"
  },
  {
    "path": "docs/tutorial/index.md",
    "chars": 412,
    "preview": "# Tutorials\n\nStep-by-step lessons that take you from zero to productive with pipx. Start from the top if you are new.\n\n-"
  },
  {
    "path": "docs/tutorial/install-applications.md",
    "chars": 1078,
    "preview": "## Installing a Package and its Applications\n\nInstall an application with:\n\n```\npipx install PACKAGE\n```\n\npipx creates a"
  },
  {
    "path": "docs/tutorial/run-applications.md",
    "chars": 3190,
    "preview": "## Running an Application in a Temporary Virtual Environment\n\n`pipx run` downloads and runs Python applications in a one"
  },
  {
    "path": "get-pipx.py",
    "chars": 343,
    "preview": "#!/usr/bin/env python3\nimport sys\n\n\ndef fail(msg):\n    sys.stderr.write(msg + \"\\n\")\n    sys.stderr.flush()\n    sys.exit("
  },
  {
    "path": "mkdocs.yml",
    "chars": 2554,
    "preview": "site_name: pipx\nsite_description: execute binaries from Python packages in isolated environments\ntheme:\n  name: \"materia"
  },
  {
    "path": "pyproject.toml",
    "chars": 3645,
    "preview": "[build-system]\nbuild-backend = \"hatchling.build\"\nrequires = [\n  \"hatch-vcs>=0.4\",\n  \"hatchling>=1.27\",\n]\n\n[project]\nname"
  },
  {
    "path": "scripts/gen_doc_pages.py",
    "chars": 1624,
    "preview": "import os\nimport sys\nfrom pathlib import Path\nfrom subprocess import check_output\nfrom typing import Optional\n\nimport mk"
  },
  {
    "path": "scripts/generate_man.py",
    "chars": 1037,
    "preview": "#!/usr/bin/env python3\n\nimport os.path\nimport sys\nimport textwrap\nfrom typing import cast\n\nfrom build_manpages.manpage i"
  },
  {
    "path": "scripts/list_test_packages.py",
    "chars": 5340,
    "preview": "#!/usr/bin/env python3\nimport argparse\nimport re\nimport subprocess\nimport sys\nimport tempfile\nfrom concurrent.futures im"
  },
  {
    "path": "scripts/migrate_pipsi_to_pipx.py",
    "chars": 1895,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"\nScript to migrate from pipsi to pipx\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nfrom pathli"
  },
  {
    "path": "scripts/release.py",
    "chars": 3215,
    "preview": "\"\"\"Handles creating a release.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom subprocess import c"
  },
  {
    "path": "scripts/templates/docs.md",
    "chars": 639,
    "preview": "{{usage}}\n\n### pipx install\n\n{{install}}\n\n### pipx install-all\n\n{{installall}}\n\n### pipx uninject\n\n{{uninject}}\n\n### pip"
  },
  {
    "path": "scripts/test_packages_support.py",
    "chars": 610,
    "preview": "import platform\nimport sys\nfrom pathlib import Path\n\nPYTHON_VERSION_STR = f\"{sys.version_info[0]}.{sys.version_info[1]}\""
  },
  {
    "path": "scripts/update_package_cache.py",
    "chars": 6533,
    "preview": "#!/usr/bin/env python3\nimport argparse\nimport re\nimport subprocess\nimport sys\nfrom concurrent.futures import ThreadPoolE"
  },
  {
    "path": "src/pipx/__init__.py",
    "chars": 173,
    "preview": "import sys\n\nif sys.version_info < (3, 9, 0):  # noqa: UP036\n    sys.exit(\"Python 3.9 or later is required. See https://g"
  },
  {
    "path": "src/pipx/__main__.py",
    "chars": 403,
    "preview": "import os\nimport sys\n\nif not __spec__ or not __spec__.parent:\n    # Running from source. Add pipx's source code to the s"
  },
  {
    "path": "src/pipx/animate.py",
    "chars": 3953,
    "preview": "import logging\nimport shutil\nimport sys\nfrom collections.abc import Generator\nfrom contextlib import contextmanager\nfrom"
  },
  {
    "path": "src/pipx/colors.py",
    "chars": 846,
    "preview": "import sys\nfrom typing import Callable\n\ntry:\n    import colorama  # type: ignore[import-untyped]\nexcept ImportError:  # "
  },
  {
    "path": "src/pipx/commands/__init__.py",
    "chars": 1112,
    "preview": "from pipx.commands.ensure_path import ensure_pipx_paths\nfrom pipx.commands.environment import environment\nfrom pipx.comm"
  },
  {
    "path": "src/pipx/commands/common.py",
    "chars": 17294,
    "preview": "import filecmp\nimport logging\nimport os\nimport shlex\nimport shutil\nimport sys\nimport tempfile\nimport time\nfrom pathlib i"
  },
  {
    "path": "src/pipx/commands/ensure_path.py",
    "chars": 5379,
    "preview": "import logging\nimport site\nimport sys\nfrom pathlib import Path\nfrom typing import Optional\n\nimport userpath  # type: ign"
  },
  {
    "path": "src/pipx/commands/environment.py",
    "chars": 2144,
    "preview": "import os\n\nfrom pipx import paths\nfrom pipx.constants import EXIT_CODE_OK, ExitCode\nfrom pipx.emojis import EMOJI_SUPPOR"
  },
  {
    "path": "src/pipx/commands/inject.py",
    "chars": 5237,
    "preview": "import logging\nimport os\nimport re\nimport sys\nfrom collections.abc import Generator, Iterable\nfrom pathlib import Path\nf"
  },
  {
    "path": "src/pipx/commands/install.py",
    "chars": 8801,
    "preview": "import json\nimport sys\nfrom collections.abc import Iterator\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom p"
  },
  {
    "path": "src/pipx/commands/interpreter.py",
    "chars": 5809,
    "preview": "import logging\nimport subprocess\nfrom pathlib import Path\n\nfrom packaging import version\n\nfrom pipx import commands, con"
  },
  {
    "path": "src/pipx/commands/list_packages.py",
    "chars": 5708,
    "preview": "import json\nimport logging\nimport sys\nfrom collections.abc import Collection\nfrom pathlib import Path\nfrom typing import"
  },
  {
    "path": "src/pipx/commands/pin.py",
    "chars": 3662,
    "preview": "import logging\nfrom collections.abc import Sequence\nfrom pathlib import Path\n\nfrom pipx.colors import bold\nfrom pipx.con"
  },
  {
    "path": "src/pipx/commands/reinstall.py",
    "chars": 4885,
    "preview": "import sys\nfrom collections.abc import Sequence\nfrom pathlib import Path\n\nfrom packaging.utils import canonicalize_name\n"
  },
  {
    "path": "src/pipx/commands/run.py",
    "chars": 13431,
    "preview": "import datetime\nimport hashlib\nimport logging\nimport re\nimport sys\nimport time\nimport urllib.parse\nimport urllib.request"
  },
  {
    "path": "src/pipx/commands/run_pip.py",
    "chars": 502,
    "preview": "from pathlib import Path\n\nfrom pipx.constants import ExitCode\nfrom pipx.util import PipxError\nfrom pipx.venv import Venv"
  },
  {
    "path": "src/pipx/commands/uninject.py",
    "chars": 4873,
    "preview": "import logging\nimport os\nfrom pathlib import Path\n\nfrom packaging.utils import canonicalize_name\n\nfrom pipx.colors impor"
  },
  {
    "path": "src/pipx/commands/uninstall.py",
    "chars": 6355,
    "preview": "import logging\nfrom pathlib import Path\nfrom shutil import which\nfrom typing import TYPE_CHECKING, Optional\n\nif TYPE_CHE"
  },
  {
    "path": "src/pipx/commands/upgrade.py",
    "chars": 9211,
    "preview": "import logging\nimport os\nimport sys\nfrom collections.abc import Sequence\nfrom pathlib import Path\nfrom typing import Opt"
  },
  {
    "path": "src/pipx/constants.py",
    "chars": 2321,
    "preview": "import os\nimport platform\nimport sysconfig\nfrom textwrap import dedent\nfrom typing import NewType\n\nPIPX_SHARED_PTH = \"pi"
  },
  {
    "path": "src/pipx/emojis.py",
    "chars": 897,
    "preview": "import os\nimport sys\n\n\ndef strtobool(val: str) -> bool:\n    val = val.lower()\n    if val in (\"y\", \"yes\", \"t\", \"true\", \"o"
  },
  {
    "path": "src/pipx/interpreter.py",
    "chars": 6651,
    "preview": "import logging\nimport os\nimport shutil\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom typing import Optional"
  },
  {
    "path": "src/pipx/main.py",
    "chars": 45006,
    "preview": "# PYTHON_ARGCOMPLETE_OK\n\n\"\"\"The command line interface to pipx\"\"\"\n\nimport argparse\nimport logging\nimport logging.config\n"
  },
  {
    "path": "src/pipx/package_specifier.py",
    "chars": 9316,
    "preview": "# Valid package specifiers for pipx:\n#   PEP508-compliant\n#   git+<URL>\n#   <URL>\n#   <pypi_package_name>\n#   <pypi_pack"
  },
  {
    "path": "src/pipx/paths.py",
    "chars": 6990,
    "preview": "import logging\nimport os\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom platformdirs import user_cache_path,"
  },
  {
    "path": "src/pipx/pipx_metadata_file.py",
    "chars": 7209,
    "preview": "import json\nimport logging\nfrom dataclasses import asdict, dataclass, field\nfrom pathlib import Path\nfrom typing import "
  },
  {
    "path": "src/pipx/shared_libs.py",
    "chars": 6934,
    "preview": "import datetime\nimport logging\nimport time\nfrom configparser import ConfigParser\nfrom contextlib import suppress\nfrom pa"
  },
  {
    "path": "src/pipx/standalone_python.py",
    "chars": 8056,
    "preview": "import datetime\nimport hashlib\nimport json\nimport logging\nimport platform\nimport re\nimport shutil\nimport tarfile\nimport "
  },
  {
    "path": "src/pipx/util.py",
    "chars": 15087,
    "preview": "import logging\nimport os\nimport random\nimport re\nimport shutil\nimport string\nimport subprocess\nimport sys\nimport textwra"
  },
  {
    "path": "src/pipx/venv.py",
    "chars": 19878,
    "preview": "import json\nimport logging\nimport re\nimport shutil\nimport time\nfrom collections.abc import Generator\nfrom pathlib import"
  },
  {
    "path": "src/pipx/venv_inspect.py",
    "chars": 12246,
    "preview": "import json\nimport logging\nimport textwrap\nfrom collections.abc import Collection\nfrom pathlib import Path\nfrom typing i"
  },
  {
    "path": "src/pipx/version.pyi",
    "chars": 242,
    "preview": "version: str\nversion_tuple: tuple[int, int, int, str, str] | tuple[int, int, int]\n\n# Note that newer versions of setupto"
  },
  {
    "path": "testdata/empty_project/README.md",
    "chars": 36,
    "preview": "Empty project used for testing only\n"
  },
  {
    "path": "testdata/empty_project/empty_project/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "testdata/empty_project/empty_project/main.py",
    "chars": 61,
    "preview": "def main():\n    pass\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "testdata/empty_project/pyproject.toml",
    "chars": 709,
    "preview": "[build-system]\nbuild-backend = \"setuptools.build_meta\"\nrequires = [\n  \"setuptools\",\n]\n\n[project]\nname = \"empty-project\"\n"
  },
  {
    "path": "testdata/pipx_metadata_multiple_errors.json",
    "chars": 2195,
    "preview": "{\n    \"pipx_spec_version\": \"0.1\",\n    \"venvs\": {\n        \"dotenv\": {\n            \"metadata\": {\n                \"injected"
  },
  {
    "path": "testdata/standalone_python_index_20250818.json",
    "chars": 153597,
    "preview": "{\"fetched\": 1756161930.970887, \"releases\": [[\"https://github.com/astral-sh/python-build-standalone/releases/download/202"
  },
  {
    "path": "testdata/standalone_python_index_20250828.json",
    "chars": 153597,
    "preview": "{\"fetched\": 1756410972.792111, \"releases\": [[\"https://github.com/astral-sh/python-build-standalone/releases/download/202"
  },
  {
    "path": "testdata/test_package_specifier/local_extras/repeatme/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "testdata/test_package_specifier/local_extras/repeatme/main.py",
    "chars": 282,
    "preview": "import sys\n\ntry:\n    import pycowsay.main\n\n    has_pycowsay = True\nexcept ImportError:\n    has_pycowsay = False\n\n\ndef ma"
  },
  {
    "path": "testdata/test_package_specifier/local_extras/setup.py",
    "chars": 263,
    "preview": "from setuptools import setup\n\nsetup(\n    name=\"repeatme\",\n    version=0.1,\n    description=\"Repeat arguments.\",\n    pack"
  },
  {
    "path": "testdata/tests_packages/README.md",
    "chars": 1284,
    "preview": "# Introduction\n\n`primary_packages.txt` is the master list, containing all packages\ninstalled or injected in the pipx tes"
  },
  {
    "path": "tests/conftest.py",
    "chars": 8478,
    "preview": "import json\nimport os\nimport shutil\nimport socket\nimport subprocess\nimport sys\nfrom collections.abc import Iterator\nfrom"
  },
  {
    "path": "tests/helpers.py",
    "chars": 7905,
    "preview": "import json\nimport os\nimport re\nimport sys\nfrom dataclasses import replace\nfrom pathlib import Path\nfrom typing import A"
  },
  {
    "path": "tests/package_info.py",
    "chars": 28486,
    "preview": "import sys\nfrom pathlib import Path\nfrom typing import Any\n\nWIN = sys.platform.startswith(\"win\")\n\n\ndef _exe_if_win(apps)"
  },
  {
    "path": "tests/test_animate.py",
    "chars": 4813,
    "preview": "import time\nfrom timeit import default_timer\n\nimport pytest  # type: ignore[import-not-found]\n\nimport pipx.animate\nfrom "
  },
  {
    "path": "tests/test_common.py",
    "chars": 532,
    "preview": "from helpers import skip_if_windows\nfrom pipx.commands.common import get_exposed_paths_for_package\n\n\n@skip_if_windows\nde"
  },
  {
    "path": "tests/test_completions.py",
    "chars": 207,
    "preview": "from helpers import run_pipx_cli\n\n\ndef test_cli(monkeypatch, capsys):\n    assert not run_pipx_cli([\"completions\"])\n    c"
  },
  {
    "path": "tests/test_emojis.py",
    "chars": 1484,
    "preview": "import sys\nfrom io import BytesIO, TextIOWrapper\nfrom unittest import mock\n\nimport pytest  # type: ignore[import-not-fou"
  },
  {
    "path": "tests/test_environment.py",
    "chars": 4220,
    "preview": "import fnmatch\nfrom pathlib import Path\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import run_pipx_c"
  },
  {
    "path": "tests/test_inject.py",
    "chars": 4162,
    "preview": "import logging\nimport re\nimport textwrap\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import PIPX_META"
  },
  {
    "path": "tests/test_install.py",
    "chars": 20207,
    "preview": "import os\nimport re\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom unittest import mock\n\nimport pytest  # ty"
  },
  {
    "path": "tests/test_install_all.py",
    "chars": 1387,
    "preview": "from pathlib import Path\n\nfrom helpers import run_pipx_cli\nfrom pipx import paths\n\n\ndef test_install_all(pipx_temp_env, "
  },
  {
    "path": "tests/test_install_all_packages.py",
    "chars": 18496,
    "preview": "\"\"\"\nThis module uses the pytest infrastructure to produce reports on a large list\nof packages.  It verifies installation"
  },
  {
    "path": "tests/test_interpreter.py",
    "chars": 6839,
    "preview": "import shutil\nimport subprocess\nimport sys\nfrom unittest.mock import Mock\n\nimport pytest  # type: ignore[import-not-foun"
  },
  {
    "path": "tests/test_list.py",
    "chars": 8487,
    "preview": "import json\nimport os\nimport re\nimport shutil\nimport sys\nimport time\n\nimport pytest  # type: ignore[import-not-found]\n\nf"
  },
  {
    "path": "tests/test_main.py",
    "chars": 1449,
    "preview": "import sys\nfrom unittest import mock\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import run_pipx_cli\n"
  },
  {
    "path": "tests/test_package_specifier.py",
    "chars": 9516,
    "preview": "from pathlib import Path\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom pipx.package_specifier import (\n    fix_"
  },
  {
    "path": "tests/test_pin.py",
    "chars": 2217,
    "preview": "from helpers import run_pipx_cli\nfrom package_info import PKG\n\n\ndef test_pin(capsys, pipx_temp_env, caplog):\n    assert "
  },
  {
    "path": "tests/test_pipx_metadata_file.py",
    "chars": 3950,
    "preview": "import sys\nfrom dataclasses import replace\nfrom pathlib import Path\n\nimport pytest  # type: ignore[import-not-found]\n\nfr"
  },
  {
    "path": "tests/test_reinstall.py",
    "chars": 3072,
    "preview": "import sys\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import PIPX_METADATA_LEGACY_VERSIONS, mock_leg"
  },
  {
    "path": "tests/test_reinstall_all.py",
    "chars": 1863,
    "preview": "import sys\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import PIPX_METADATA_LEGACY_VERSIONS, mock_leg"
  },
  {
    "path": "tests/test_run.py",
    "chars": 18088,
    "preview": "import logging\nimport os\nimport subprocess\nimport sys\nimport textwrap\nfrom pathlib import Path\nfrom unittest import mock"
  },
  {
    "path": "tests/test_runpip.py",
    "chars": 641,
    "preview": "from helpers import run_pipx_cli, skip_if_windows\n\n\ndef test_runpip(pipx_temp_env, monkeypatch, capsys):\n    assert not "
  },
  {
    "path": "tests/test_shared_libs.py",
    "chars": 2814,
    "preview": "import os\nimport time\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nimport pytest  # type: ignore[import-not"
  },
  {
    "path": "tests/test_standalone_interpreter.py",
    "chars": 4153,
    "preview": "import json\nimport shutil\nimport sys\n\nfrom helpers import (\n    run_pipx_cli,\n)\nfrom package_info import PKG\nfrom pipx i"
  },
  {
    "path": "tests/test_uninject.py",
    "chars": 1888,
    "preview": "from helpers import run_pipx_cli, skip_if_windows\nfrom package_info import PKG\n\n\ndef test_uninject_simple(pipx_temp_env,"
  },
  {
    "path": "tests/test_uninstall.py",
    "chars": 8048,
    "preview": "import sys\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import (\n    PIPX_METADATA_LEGACY_VERSIONS,\n  "
  },
  {
    "path": "tests/test_uninstall_all.py",
    "chars": 603,
    "preview": "import pytest  # type: ignore[import-not-found]\n\nfrom helpers import PIPX_METADATA_LEGACY_VERSIONS, mock_legacy_venv, ru"
  },
  {
    "path": "tests/test_unpin.py",
    "chars": 1791,
    "preview": "from helpers import run_pipx_cli\nfrom package_info import PKG\n\n\ndef test_unpin(capsys, pipx_temp_env, caplog):\n    asser"
  },
  {
    "path": "tests/test_upgrade.py",
    "chars": 6223,
    "preview": "import pytest  # type: ignore[import-not-found]\n\nfrom helpers import (\n    PIPX_METADATA_LEGACY_VERSIONS,\n    mock_legac"
  },
  {
    "path": "tests/test_upgrade_all.py",
    "chars": 1174,
    "preview": "import pytest  # type: ignore[import-not-found]\n\nfrom helpers import PIPX_METADATA_LEGACY_VERSIONS, mock_legacy_venv, ru"
  },
  {
    "path": "tests/test_upgrade_shared.py",
    "chars": 2892,
    "preview": "import subprocess\n\nimport pytest  # type: ignore[import-not-found]\n\nfrom helpers import run_pipx_cli\n\n\n@pytest.fixture\nd"
  },
  {
    "path": "tox.toml",
    "chars": 1910,
    "preview": "requires = [\"tox>=4.45\"]\n\nenv_list = [\n  \"3.13\",\n  \"3.12\",\n  \"3.11\",\n  \"3.10\",\n  \"3.9\",\n  \"lint\",\n  \"docs\",\n  \"man\",\n]\n\n"
  }
]

About this extraction

This page contains the full source code of the pypa/pipx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 139 files (885.5 KB), approximately 274.8k tokens, and a symbol index with 550 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!