Skip to content

Do I need a project to use uv?

If your Python life is a ~/bin full of short scripts that glue APIs together and rename files, uv’s first move can look like overkill: uv init, then a pyproject.toml. uv works fine for scripts, but it assumes a different mental model from the pip install habit most script authors carry, and forcing the old habit onto uv is where people get stuck.

The short answer: no, you don’t need a project. You have four options, and the right one depends on what the script is.

Why everyone reaches for one shared environment

Installing packages once and using them everywhere is a rational response to old pip. pip installed globally by default, and a virtual environment had to be re-activated in every new shell. A shebang of #!/usr/bin/env python3 pointed at the one interpreter that had every library installed, and everything worked until it didn’t.

That world is gone. Modern Python on macOS refuses pip install without a venv, which is what the externally-managed-environment error is trying to tell you. The muscle memory persists, though, which is why the first question people ask after installing uv is a version of “where do I put my libraries?”

How uv makes per-script isolation cheap

Every uv run resolves a script’s dependencies against a global wheel cache at ~/.cache/uv. The cache is shared across every script and every project on the machine, so the requests wheel used by last week’s script can also satisfy today’s. Repeat runs of the same script reuse the resolved environment in around 100ms; cold first runs take roughly a second for a handful of packages, longer if uv has to download wheels it has not seen before.

The historical argument for a shared venv was avoiding forty separate installs of requests. With uv’s cache you install it once and reuse it everywhere, and each script’s environment stays independent. See What happens when you run uv run for the full execution path.

Let each script declare its own dependencies

For a one-off script that imports a handful of third-party libraries, PEP 723 inline metadata is the right default. The script carries a commented TOML block at the top declaring its Python version and dependencies, and uv run reads that block to build the environment.

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests", "rich"]
# ///

import requests
from rich import print

chmod +x myscript.py, then ./myscript.py runs the script the same way it always did.

The cost is a four-line header and a shebang edit per script. For existing scripts, that’s an afternoon of migration. For new scripts, uv add --script myscript.py requests writes the header for you. See How to write a self-contained Python script for the full walkthrough.

Share one venv across a batch of scripts

If you have a dozen automation scripts that all use requests, click, and rich, and none of them care which version, a single shared virtual environment is a reasonable choice.

uv venv ~/.venvs/scripts
uv pip install --python ~/.venvs/scripts/bin/python requests click rich

Shebang each script at the absolute interpreter path:

#!/Users/you/.venvs/scripts/bin/python

This keeps the mental model you grew up with, minus the fragility: Homebrew upgrading Python no longer wipes your packages, because the venv holds its own copy of the interpreter.

Eventually one script needs pydantic==1.x while another needs pydantic>=2, and the shared venv can’t hold both. Then you split the venv or migrate the conflicting scripts to PEP 723. Pick this pattern when you own the scripts and update them together; avoid it for scripts you want to leave alone for years.

Put your scripts in a uv project

The reproducible cousin of the shared venv is a uv project whose only purpose is to host your scripts. One directory contains a pyproject.toml listing every dependency and a uv.lock pinning exact versions; the scripts sit alongside. uv run ./myscript.py from that directory picks up the project environment automatically.

The value over a raw shared venv is uv sync. Check the directory into a dotfiles repo or a private GitHub repo, clone it onto a new Mac, run uv sync, and the Python version and dependencies match exactly. The shared-venv pattern gives you neither the pin file nor the reproducible rebuild.

The trade-off is that running a script requires cd-ing into the directory or passing the project path. A one-line shell function smooths that over:

myscript() {
    name=$1; shift
    uv run --project ~/scripts -- python ~/scripts/"$name".py "$@"
}

Install packaged CLI tools with uv tool

A different category of “script” is the published CLI: ruff, prek, httpie, yt-dlp, black. These are packaged projects distributed on PyPI with proper entry points. uv tool install httpie builds an isolated venv for httpie and installs its binary into ~/.local/bin. Upgrading one tool never affects another.

uv tool install replaces pipx and brew install for Python CLIs. On a fresh setup, run uv tool update-shell once so ~/.local/bin is on your PATH. Reserve PEP 723 and shared-venv patterns for your own ad-hoc scripts; use uv tool install for tools someone else packaged.

Skip uv pip install --system

uv accepts a --system flag that installs packages into whichever Python interpreter is on your PATH. On macOS that’s typically Homebrew’s Python or the Xcode Command Line Tools stub, not a Python you control. Using it recreates the fragility that drove you to uv: the next brew upgrade (or Xcode CLT reinstall) bumps Python to a new minor version and removes the packages. Shebang scripts pointing at python3 then fail with ModuleNotFoundError.

pip install --break-system-packages exists for the same reason and fails the same way. See Should I use Homebrew to install Python? for the background.

Pick the option that fits the script

Match the pattern to what the script actually is:

  • One-off script with a few dependencies: PEP 723 inline metadata. Same for any script you want to still run in three years, since it carries its own spec.
  • Related personal scripts that move together: a shared venv at ~/.venvs/scripts.
  • Script collection you want to rebuild on another machine: a uv project with pyproject.toml and uv.lock.
  • Published CLI tool on PyPI: uv tool install.

Learn More

Last updated on

Please submit corrections and feedback...