The fastest way to break a developer machine is to pip install a command-line tool into the system Python. Do it a few times and you have a global site-packages where ruff, httpie and whatever else all fight over the same dependency versions — and your actual project inherits the wreckage.
Command-line applications written in Python are not project dependencies. They are tools. You want them on your PATH, isolated from each other, isolated from whatever virtual environment your current project happens to have active, and upgradeable without a dependency-resolution argument. That is exactly the job pipx was built for, and it is the layer most teams get wrong — either by installing tools globally or by cramming them into every project’s requirements.txt where they do not belong. This is how to run that layer cleanly, and when to swap pipx for the newer uv tool.
What pipx actually does
pipx installs each application into its own virtual environment and then exposes only that application’s entry-point scripts on your PATH. Install ten tools and you get ten isolated venvs under ~/.local/pipx/venvs/ (or the platform equivalent), with symlinks to their binaries in ~/.local/bin. No shared dependency tree, so ruff pinning one version of a library can never break httpie pinning another. Uninstall a tool and its entire environment goes with it.
Free · 4 minutes
Is your engineering team shipping safely, or quietly accumulating risk?
Fourteen questions on how work gets from idea to production — cadence, testing, rollback, and the key-person risk in your delivery. Banded finding on screen, full sheet by email.
Install pipx itself — and only pipx — with your platform’s package manager where you can, so it is not entangled with a project interpreter:
# macOS
brew install pipx
# Debian / Ubuntu
sudo apt install pipx
# Anywhere with a Python, as a last resort
python3 -m pip install --user pipx
# Put pipx's bin directory on PATH (edits your shell profile)
pipx ensurepath
pipx ensurepath is the step people skip and then wonder why ruff: command not found greets them. It appends the pipx bin directory to your shell profile. Open a new shell afterwards so the change takes effect — it does not touch the current session.
Installing, injecting and upgrading tools
The day-to-day surface is small. Install a tool, list what you have, upgrade one or all of them, and remove what you no longer use:
# One isolated environment per tool
pipx install ruff
pipx install httpie
pipx install pre-commit
# See every tool, its version and its Python
pipx list
# Upgrade one, or everything you manage
pipx upgrade ruff
pipx upgrade-all
# Remove a tool and its whole venv
pipx uninstall httpie
# Pin a version when you need reproducibility
pipx install "ruff==0.6.9"
The less obvious command is inject, and it earns its place. Some tools need an extra package inside their own environment — a plugin, an optional backend — without you polluting anything else. pipx inject adds a dependency into an already-installed tool’s venv:
# Add a plugin into an existing tool's environment
pipx inject mkdocs mkdocs-material
# Inject and expose the injected package's own scripts on PATH
pipx inject jupyter jupyterlab-git --include-apps
# httpie with an extra auth plugin, isolated to httpie's venv
pipx inject httpie httpie-oauth
That is the whole model: one environment per tool, inject for anything that has to live alongside a specific tool, and upgrade-all to keep the lot current. Nothing reaches your project environments, and nothing reaches your system Python.
Where this pays off in a real repository
The clearest example is your linting and formatting layer. If you have migrated to Ruff as your single linter and formatter, you want ruff available as a command on every machine and in CI, not pinned into each project’s dependencies where a version bump becomes a merge conflict. Installed via pipx, it is a tool you upgrade on your own schedule.
pre-commit is the same story. It orchestrates hooks but is itself a standalone application, so it belongs in the tool layer. A pre-commit baseline that actually runs assumes the pre-commit binary is on PATH for every contributor; pipx makes that a one-liner rather than a per-project install. And when a hook misbehaves — the classic failure on files you did not touch — you want to be certain the tool itself is a known, isolated version, not whatever leaked in from a project venv.
When uv tool replaces it
uv, Astral’s Rust-based package manager, ships the same capability as uv tool, and it is materially faster because it uses uv’s resolver and a shared global cache instead of a fresh pip run per environment. The command surface maps almost one-to-one, which makes a switch cheap:
# Install into an isolated environment (the pipx install analogue)
uv tool install ruff
uv tool install pre-commit
# List, upgrade, uninstall
uv tool list
uv tool upgrade ruff
uv tool upgrade --all
uv tool uninstall ruff
# Inject an extra dependency (uv's --with)
uv tool install mkdocs --with mkdocs-material
# Put uv's tool bin directory on PATH (pipx ensurepath analogue)
uv tool update-shell
# Run a tool one-off without installing it at all
uvx ruff check .
The genuinely new move is uvx — shorthand for uv tool run — which fetches and runs a tool in an ephemeral environment for a single invocation, cached for next time. That is the right shape for a tool you use occasionally or in CI, where a persistent install buys you nothing.
The decision is not close if you are already reaching for uv to manage projects and Python versions: standardise on uv tool and uvx and drop pipx, so one binary owns your entire Python toolchain. If you are not using uv elsewhere, pipx remains the smaller, single-purpose, batteries-included choice and there is no urgency to move. What you should not do is run both against the same tools — pick one owner for the tool layer, or you are back to two systems disagreeing about which ruff is on your PATH.
Either way, the principle holds: command-line Python apps get their own isolated environments and never touch your project dependencies. Get that boundary right once and the whole class of “it works on my machine” tooling problems stops happening.
Free interactive tool
Website compliance checklist
What your site has to do, based on what it actually does
Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.
Everything that applies
Ordered by what to do first: legal requirements you can close quickly, then larger pieces of work, then what is expected rather than required. Not exhaustive, and not a legal audit.
Dated PDF, yours to keep or circulate.
Most technology problems are not technology problems. They are control problems.
The systems exist. The investment has been made. The question is whether leadership can understand, direct, evidence, and sustain what those systems produce. Find out where control exists — and where it only appears to.