Install and Run Python Applications in Isolated Environments

RAW Doc

File: docs/explanation/comparisons.rst

#############
Comparisons
#############


pipx vs pip


  • pip is a general Python package installer. It can install libraries or CLI applications with entry points.
  • pipx is a specialized package installer. It can only install packages with CLI entry points.
  • 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 installs CLI applications but not libraries that you import in your
    code.
  • You can install pipx with pip.

Example: install pipx with pip, pip install --user pipx.


pipx vs uv tool


Both uv tool <https://docs.astral.sh/uv/concepts/tools/>_ and pipx install a Python tool into its own virtual
environment and expose the tool's console scripts on PATH. Both have a one-shot run mode (uvx and pipx run);
uvx is uv tool run. They differ in where state lives, which extra commands they ship, and how they handle managed
Python.

pipx keeps the same CLI across pip and uv backends; pipx install pipx[uv] opts you into uv-speed venv creation
without changing any commands. uv tool ships a smaller per-tool surface, then reuses the rest of uv for free:
managed Python, content-addressed cache, lockfiles, PEP 723 script handling.

Where state lives

.. list-table::
:header-rows: 1
:widths: 24 38 38

text
- - State
  - pipx
  - uv tool
- - Per-tool venvs
  - ``$PIPX_HOME/venvs/<name>`` (``PIPX_HOME``)
  - ``$UV_TOOL_DIR/<name>`` (``UV_TOOL_DIR``)
- - Exposed binaries
  - ``$PIPX_BIN_DIR`` (default ``~/.local/bin``)
  - ``$UV_TOOL_BIN_DIR`` (default same)
- - Man pages
  - ``$PIPX_MAN_DIR`` (default ``~/.local/share/man``)
  - *not exposed*
- - Shared pip/setuptools/wheel
  - ``$PIPX_HOME/shared`` (pip backend only)
  - *none; uv venvs ship without pip*
- - Ephemeral run cache
  - ``$PIPX_HOME/.cache`` (TTL 14 days)
  - ``$UV_CACHE_DIR`` (no TTL; ``uv cache prune``)
- - Standalone Python
  - ``$PIPX_HOME/py`` (``PIPX_FETCH_PYTHON``)
  - ``$UV_PYTHON_INSTALL_DIR``
- - System-wide install
  - ``--global``, ``PIPX_GLOBAL_*``
  - *not supported*

PIPX_BIN_DIR and UV_TOOL_BIN_DIR both default to ~/.local/bin on Unix, so installing the same tool with both
managers writes the same filename. Each manager refuses to overwrite a binary the other one wrote without --force.
Use pipx install --suffix=... to keep two copies side-by-side; uv has no equivalent.

Subcommand mapping

.. list-table::
:header-rows: 1
:widths: 26 37 37

text
- - Task
  - pipx
  - uv tool
- - Install from PyPI
  - ``pipx install ruff``
  - ``uv tool install ruff`` (or ``uvx ruff`` for one-off)
- - Install from a git URL
  - ``pipx install 'git+https://…'``
  - ``uv tool install 'git+https://…'``
- - Install editable from path
  - ``pipx install -e ./mypkg``
  - ``uv tool install -e ./mypkg``
- - One-off run (no install)
  - ``pipx run black .``
  - ``uvx black .``
- - Refresh one-off run
  - ``pipx run --refresh black .``
  - ``uvx --refresh black .``
- - Show ephemeral cache
  - ``pipx cache dir``
  - ``uv cache dir``
- - Purge ephemeral cache
  - ``pipx cache purge``
  - ``uv cache clean``
- - One-off run with extra dep
  - ``pipx run --with mkdocs-material mkdocs``
  - ``uvx --with mkdocs-material mkdocs``
- - Pinned-version one-off
  - ``pipx run --spec 'ruff==0.6.0' ruff check``
  - ``uvx [email protected] check``
- - Add a dep to existing tool
  - ``pipx inject mkdocs mkdocs-material``
  - ``uv tool install mkdocs --with mkdocs-material`` (rebuilds)
- - Remove an injected dep
  - ``pipx uninject mkdocs mkdocs-material``
  - *rebuild without* ``--with``
- - Upgrade one
  - ``pipx upgrade ruff``
  - ``uv tool upgrade ruff``
- - Upgrade all
  - ``pipx upgrade-all``
  - ``uv tool upgrade --all``
- - List installed or outdated
  - ``pipx list`` (``--outdated``, ``--output json``)
  - ``uv tool list`` (``--show-with``, ``--outdated``, …)
- - Diagnose broken environments
  - ``pipx health``
  - *no equivalent*
- - Repair broken environments
  - ``pipx repair ruff`` / ``repair``
  - ``uv tool install ruff`` / *no bulk equivalent*
- - Reinstall any environment
  - ``pipx reinstall ruff`` / ``reinstall-all``
  - ``uv tool upgrade --reinstall ruff`` / ``--all``
- - Run pip inside a venv
  - ``pipx runpip <tool> -- pip ...``
  - *not supported (no pip in uv venvs)*
- - PATH setup
  - ``pipx ensurepath``
  - ``uv tool update-shell``
- - Show resolved env
  - ``pipx environment``
  - ``uv tool dir``, ``uv tool dir --bin``, ``uv cache dir``, ``uv python dir``
- - PEP 723 inline script
  - ``pipx run script.py`` (with uv backend uses ``uv run``)
  - ``uv run --script script.py``

Only in pipx

  • pipx inject / uninject add or remove a package in place. uv tool install --with reaches the same end
    state by rebuilding the venv.
  • pipx runpip <venv> -- ... runs pip inside a tool's venv. uv venvs have no pip.
  • --include-deps exposes entry points from every dependency. uv requires you to enumerate dep packages with
    --with-executables-from.
  • --suffix keeps two copies of the same tool side-by-side.
  • --global and the PIPX_GLOBAL_* variables drive a system-wide install.
  • Manual pages get symlinked under $PIPX_MAN_DIR.
  • pipx manifest sync <manifest> applies an explicit desired set; pipx manifest lock <manifest> writes one PEP
    751 lock per selected tool.
  • pipx install-all <spec.json> rebuilds every venv from a pipx list --output json snapshot for cross-machine
    migration.
  • [project.entry-points."pipx.run"] declares pipx-specific runtime extras in the package metadata.
  • pipx environment prints every variable and its resolved value in one place.
  • --cooldown DAYS provides the same release-age policy through pip and uv.

Only in uv tool

  • uv tool list toggles columns via --show-with, --show-paths, --show-version-specifiers,
    --show-extras, --show-python.
  • uvx --with-editable PATH adds editable extras for a one-off run.
  • uv tool upgrade --all -p 3.13 re-pins every tool to a different Python in one shot.
  • uv python install/list/find/pin/upgrade/uninstall integrates managed Python; uv auto-fetches when the requested
    Python isn't installed.
  • --torch-backend and --isolated add controls that pipx does not expose.
  • The content-addressed cache spans uv pip, uv tool, uv run, and uv venv. Wheels downloaded once get
    reused everywhere.

Gotchas

  • uvx reuses cached envs across invocations until you prune the cache (uv cache clean), pin a new version
    (uvx black@latest), or pass --refresh. pipx run caches for 14 days and accepts --refresh for an early
    replacement.
  • uvx prefers a persistent install when one exists. After uv tool install ruff, plain uvx ruff reuses that
    env instead of building an ephemeral one. Pass --isolated to bypass.
  • uv tool ignores project-local .python-version files. uv run honors them; tool envs do not. pipx never
    reads them; pass --python or set PIPX_DEFAULT_PYTHON.
  • uv python upgrade only bumps patch versions. To move a tool from 3.12 to 3.13 run uv tool upgrade --all -p 3.13. pipx's equivalent is reinstall-all --python python3.13.
  • uv run --script needs a real on-disk path. When pipx run script.py content arrives via URL or named pipe, the
    uv backend falls back to building a venv.

Picking one

pipx wins when you need its tool-specific extras: inject/uninject, --global, --suffix, manual pages, or
pipx install-all for migration. Install pipx[uv] to keep that surface and pick up uv-speed venv creation. Reach
for uv tool when you already drive uv for managed Python or uv run --script and want one binary for everything.
Running both is fine; the only collision point is the shared bin dir, and both sides refuse to overwrite without
--force.


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: install poetry with pipx, pipx install poetry; run poetry without installing it, 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 relies heavily on the venv package.

Example: pipx installs packages into 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.11, 3.12, and so on.
  • pipx installs packages in virtual environments and exposes their entry points on your PATH.

Example: install a Python interpreter with pyenv, then install a package with pipx using that interpreter, pipx install black --python=python3.11 where python3.11 was installed by 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 can run an app in one line, leaving your system unchanged after it finishes (pipx run APP); pipsi cannot.
  • pipx can recursively install binaries from dependent packages.
  • pipx has more CLI options such as upgrade-all, reinstall-all, and uninstall-all.
  • pipx is more modern. It requires Python 3.10+ and uses the standard-library venv package.
  • pipx works with Python homebrew installations while pipsi does not.
  • pipx lets you see each command it runs by passing --verbose.

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>_. You can do this with pipx itself:

.. code-block:: console

text
$ 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 from different sources: brew uses a curated repository specific to brew, and pipx generally uses PyPI.

Example: brew can install pipx, but the two generally do not interact much.


pipx vs npx


  • Both can run CLI tools. npx searches node_modules and otherwise runs in a temporary environment; pipx run
    searches __pypackages__ and otherwise runs in a temporary environment.
  • npx works with JavaScript and pipx works with Python.
  • Both make running executables written in a dynamic language as easy as possible.
  • pipx can also install tools globally; npx cannot.

Example: 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:

.. code-block:: bash

text
pipx run poetry --help
pip-run poetry -- -m poetry --help

pipx vs fades


fades <https://github.com/PyAr/fades>_ runs 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 :doc:pipx run <../reference/examples> let you specify 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.
  • Both tools automatically set up reusable virtualenvs containing the necessary dependencies.
  • Both can download Python scripts or packages to execute from remote resources.
  • fades can only run individual script files while pipx can also run packages.

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 detailed comparison <https://github.com/cynic-net/pactivate/blob/main/doc/vs-pipx.md>_, but to summarize:

Similarities:

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

pae advantages:

  • Supports all versions of Python from 2.7 upward. pipx requires 3.10 or above.
  • Fewer dependencies.
  • Easier to have multiple versions of a single program, or use different Python versions for one 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 repos using pactivate <https://github.com/cynic-net/pactivate>_.

pae disadvantages:

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

File: docs/explanation/how-pipx-works.rst

################
How pipx works
################

.. note::

text
This page describes the **pip backend** model, where a shared ``pip`` environment backs every managed venv through a
``.pth`` file. When the **uv backend** is active (the default whenever uv is available, via the ``pipx[uv]`` extra
or on ``PATH``) pipx skips the shared environment and uses ``uv venv`` and ``uv pip`` instead; uv-created venvs ship
without pip. See :doc:`../how-to/use-uv-backend`.

Installing an application


pipx install PACKAGE creates an isolated virtual environment under PIPX_HOME (for example
~/.local/share/pipx/venvs/black) and installs the package into it. Under the pip backend pipx first creates or
reuses a shared environment that holds an up-to-date pip and exposes it to each managed venv through a
.pth file <https://docs.python.org/3/library/site.html>_, so every venv borrows one pip rather than installing its
own. Under the uv backend there is no shared environment: uv venv builds the venv (without pip) and uv pip
installs into it.

Once the package is installed, pipx exposes the application's resources so you can reach them from anywhere. It handles
four kinds:

  • console and GUI scripts, into PIPX_BIN_DIR (default ~/.local/bin), for example
    ~/.local/bin/black -> ~/.local/share/pipx/venvs/black/bin/black;
  • manual pages, into PIPX_MAN_DIR (default ~/.local/share/man/man[1-9]);
  • shell completion scripts, into PIPX_COMPLETION_DIR (default ~/.local/share).

pipx symlinks each resource into place. On Windows, and on any filesystem that does not support symlinks, it copies the
file instead. As long as PIPX_BIN_DIR is on your PATH the commands are available globally, and on systems with
man support the pages are too.

pipx records what it installed and exposed in a per-venv pipx_metadata.json file. Later commands (upgrade,
reinstall, expose) read it to reproduce the same state.

Adding --global to any command targets a system-wide location shared by all users instead of your home directory;
the concrete paths and overrides live in :doc:../how-to/configure-paths. It is not available on Windows.

For a .py script carrying PEP 723 metadata, pipx builds a temporary wheel from the script and its declared
dependencies, installs that wheel like any other package, then records the original path or URL so upgrade and
reinstall can rebuild it.

.. mermaid::

text
flowchart LR
    A["pipx install black"] --> BK{"backend?"}
    BK -- pip --> P["shared venv (pip)reused via .pth"]
    BK -- uv --> U["uv venv(no pip)"]
    P --> C["create venvvenvs/black/"]
    U --> C
    C --> D["install packagepip install / uv pip install"]
    D --> E["expose apps~/.local/bin"]
    D --> F["expose man pages~/.local/share/man"]
    D --> G["expose completionsPIPX_COMPLETION_DIR"]

    classDef input fill:#3f72af,stroke:#28517f,color:#fff
    classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
    classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
    classDef out fill:#388e3c,stroke:#256128,color:#fff
    class A input
    class BK decision
    class P,U,C,D proc
    class E,F,G out

Running an application


pipx run APP executes an application without installing it permanently. pipx either reuses a cached temporary venv
or builds a fresh one, then invokes the app. The cache key is a hash of the package name, spec, Python version, and pip
arguments; cached environments expire after 14 days, after which the next run rebuilds against the latest release.

Under the pip backend the temporary venv borrows the shared pip; under the uv backend uv creates and populates it.
pipx run --with PKG adds extra dependencies to that temporary environment.

.. mermaid::

text
flowchart LR
    A["pipx run pycowsay"] --> C{"cachedvenv?"}
    C -- yes --> E["reuse cached venv"]
    C -- no --> BK{"backend?"}
    BK -- pip --> P["shared pip +python -m venv"]
    BK -- uv --> U["uv venv + uv pip"]
    P --> D["install pycowsay"]
    U --> D
    D --> F["invoke app"]
    E --> F

    classDef input fill:#3f72af,stroke:#28517f,color:#fff
    classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
    classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
    classDef out fill:#388e3c,stroke:#256128,color:#fff
    class A input
    class C,BK decision
    class P,U,D,E proc
    class F out

Exposing apps on PATH


When you type an exposed command, the shell finds the symlink (or copy) in PIPX_BIN_DIR and follows it to the
launcher inside the app's venv. That launcher runs the venv's own Python against the installed package, so the app
always uses its isolated dependencies and never your system site-packages.

.. mermaid::

text
flowchart LR
    U["user typesblack"] --> B["~/.local/bin/black(symlink or copy)"]
    B --> V["venvs/black/bin/black(launcher)"]
    V --> P["venv Python+ installed black"]

    classDef input fill:#3f72af,stroke:#28517f,color:#fff
    classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
    classDef out fill:#388e3c,stroke:#256128,color:#fff
    class U input
    class B,V proc
    class P out

Directory layout


pipx keeps its state under one home directory. The tree below is the conceptual shape; the concrete locations, defaults,
and every override live in :doc:../how-to/configure-paths.

.. mermaid::

text
flowchart TD
    HOME["~"] --> BIN["~/.local/bin/(on PATH)"]
    HOME --> DATA["~/.local/share/pipx/"]
    DATA --> SHARED["shared/(pip, pip backend only)"]
    DATA --> VENVS["venvs/"]
    VENVS --> V1["black/"]
    VENVS --> V2["poetry/"]
    VENVS --> V3["ruff/"]
    V1 --> V1BIN["bin/black"]
    BIN -->|symlink or copy| V1BIN

    classDef input fill:#3f72af,stroke:#28517f,color:#fff
    classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
    classDef out fill:#388e3c,stroke:#256128,color:#fff
    classDef venv fill:#7c4dff,stroke:#5a2fd0,color:#fff
    classDef shared fill:#c78c20,stroke:#8a6011,color:#fff
    class HOME input
    class DATA,VENVS proc
    class BIN out
    class SHARED shared
    class V1,V2,V3,V1BIN venv

You can do all of this yourself; pipx automates it. Pass --verbose to see each command it runs and to stream
installer output.


Resolving the Python interpreter


pipx prefers a real system Python and only downloads a standalone build as a fallback. When you pass --python, it
tries the value as a literal path, then as a command on PATH, then as a pythonX.Y command, then through the
py launcher on Windows. Only if every step fails, and --fetch-python permits it, does pipx fetch a build from
python-build-standalone <https://github.com/astral-sh/python-build-standalone>_. The default, --fetch-python=never,
keeps pipx offline and errors out instead.

For the authoritative step-by-step order, the --fetch-python values, and when a downloaded build beats a patched
system Python, see :doc:../how-to/standalone-python.

.. mermaid::

text
flowchart TD
    S["--python VALUE"] --> A{"file exists?"}
    A -- yes --> USE["use interpreter"]
    A -- no --> B{"on PATH?shutil.which"}
    B -- yes --> USE
    B -- no --> C{"pythonX.Y?(non-Windows)"}
    C -- yes --> USE
    C -- no --> D{"py launcher?(Windows)"}
    D -- yes --> USE
    D -- no --> E{"--fetch-pythonallows download?"}
    E -- "missing / always" --> DL["download standalone build"]
    E -- "never (default)" --> ERR["error"]

    classDef input fill:#3f72af,stroke:#28517f,color:#fff
    classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
    classDef out fill:#388e3c,stroke:#256128,color:#fff
    classDef err fill:#b3261e,stroke:#7a1a15,color:#fff
    class S input
    class A,B,C,D,E decision
    class USE,DL out
    class ERR err