Modern Toolchain: uv

Updated

September 7, 2026

The Modern Toolchain with uv

After reading this chapter, you will be able to install and switch Python versions, run isolated scripts with inline dependencies without manual virtual environment management, and manage deterministic projects using uv.

Mental model

Historically, Python developers were forced to stitch together 5 to 7 separate tools: pyenv to install Python versions, virtualenv to isolate packages, pip to install wheels, pip-tools or poetry to resolve dependency locks, and twine to publish distributions.

Astral’s uv replaces this entire fragmented ecosystem with a single, ultra-fast Rust-based binary:

[ Legacy Fragmented Toolchain ]
  pyenv ──▶ virtualenv ──▶ pip ──▶ pip-tools ──▶ flit/twine
  (Slow, disparate configs, manual shell activation, fragile PATHs)

─────────────────────────────────────────────────────────────────

[ Modern Unified Architecture: uv ]

                 ┌───────────────────────────────┐
                 │       The `uv` CLI            │
                 └──────────────┬────────────────┘
                                │
        ┌───────────────────────┼───────────────────────┐
        ▼                       ▼                       ▼
 ┌──────────────┐        ┌──────────────┐        ┌──────────────┐
 │ Python Engine│        │ Environments │        │ Project Lock │
 │ `uv python`  │        │ `uv run`     │        │ `uv lock`    │
 └──────┬───────┘        └──────┬───────┘        └──────┬───────┘
        │                       │                       │
 Installs & pins         Ephemerally spins up    Resolves lockfile
 standalone CPython      or links `.venv`        (`uv.lock`) in
 3.14 without root       instantly; auto-runs    milliseconds with
 permissions.            inline PEP 723 scripts. universal wheel cache.

With uv, you no longer need to remember to “activate” a virtual environment in your shell. You prefix commands with uv run, and uv ensures the exact Python interpreter and locked dependencies are resolved automatically.


Minimal example

Save the following file as minimal_env.py. This script inspects the running Python executable and verifies whether it is executing inside an isolated virtual environment:

# minimal_env.py
import os
import sys

def main() -> None:
    in_venv = sys.prefix != sys.base_prefix
    print(f"Active Python Executable : {sys.executable}")
    print(f"Isolated Virtualenv      : {in_venv}")
    print(f"Prefix Directory         : {sys.prefix}")
    print(f"Base Interpreter Path    : {sys.base_prefix}")
    print(f"Process PID              : {os.getpid()}")

if __name__ == "__main__":
    main()

Run this script using uv run:

uv run python minimal_env.py

Output:

Active Python Executable : /home/user/myproject/.venv/bin/python
Isolated Virtualenv      : True
Prefix Directory         : /home/user/myproject/.venv
Base Interpreter Path    : /home/user/.local/share/uv/python/cpython-3.14.0...
Process PID              : 412850

Worked examples

1. Single-file scripts with inline dependencies (PEP 723)

Traditionally, sharing a Python script that required a library like httpx or rich forced you to write a separate requirements.txt file and instruct users to create and activate a virtualenv.

Under PEP 723 (supported natively by uv), you declare your script’s dependencies directly inside the file header. uv parses this header, provisions an isolated ephemeral cache, and runs the script seamlessly.

Save this file as weather_fetch.py:

# /// script
# requires-python = ">=3.14"
# dependencies = [
#     "httpx>=0.27.0",
# ]
# ///
# weather_fetch.py
import httpx

def get_current_utc_time() -> None:
    # Query a lightweight, public JSON endpoint
    url = "https://httpbin.org/get"
    headers = {"User-Agent": "ModernPythonGuide/1.0"}
    
    print(f"Sending HTTP GET request to {url}...")
    with httpx.Client(timeout=5.0) as client:
        response = client.get(url, headers=headers)
        response.raise_for_status()
        data = response.json()
        
    print(f"HTTP Status: {response.status_code}")
    print(f"Origin IP  : {data.get('origin')}")
    print(f"Headers Sent:")
    for k, v in data.get("headers", {}).items():
        print(f"  {k}: {v}")

if __name__ == "__main__":
    get_current_utc_time()

Run the script directly:

uv run weather_fetch.py

Why this matters: You did not have to pip install httpx globally. uv read the # /// script block, verified whether httpx was present in its global content-addressable cache, downloaded it in milliseconds if needed, and executed the script in complete isolation.


2. Managing Python runtimes with uv python

You do not need to install Python from your operating system’s system package manager (apt, dnf, or brew), which often ships outdated versions or risks breaking system tools.

Inspect available Python versions:

uv python list

Install Python 3.14 directly into an unprivileged user directory:

uv python install 3.14

Pin your project directory to use Python 3.14:

uv python pin 3.14

This creates a .python-version file containing:

3.14

Now, every uv command executed in this folder or its subfolders will automatically invoke Python 3.14.


3. Creating and locking a modern project

To start a production project, initialize a standard workspace:

uv init my_app
cd my_app

This generates a minimal, standard pyproject.toml:

[project]
name = "my-app"
version = "0.1.0"
description = "Production application scaffold"
readme = "README.md"
requires-python = ">=3.14"
dependencies = []

Add a dependency:

uv add "pydantic>=2.10.0"

Instantly, uv performs dependency resolution, updates pyproject.toml, creates a local .venv/, and generates a deterministic uv.lock.

Save this code in my_app/main.py:

# main.py
from pydantic import BaseModel, Field

class ServiceNode(BaseModel):
    hostname: str
    ip_address: str
    port: int = Field(default=8080, ge=1, le=65535)
    is_active: bool = True

def main() -> None:
    node = ServiceNode(hostname="edge-router-01", ip_address="192.168.1.1", port=443)
    print(f"Validated Node: {node.hostname} -> {node.ip_address}:{node.port} (active={node.is_active})")
    print(f"JSON Payload  : {node.model_dump_json()}")

if __name__ == "__main__":
    main()

Run your application:

uv run python main.py

Output:

Validated Node: edge-router-01 -> 192.168.1.1:443 (active=True)
JSON Payload  : {"hostname":"edge-router-01","ip_address":"192.168.1.1","port":443,"is_active":true}

Pitfalls

1. Manually activating virtualenvs in every terminal tab

The Trap: Running source .venv/bin/activate, opening a new terminal tab, forgetting to activate, and running against system Python.
The Fix: Never rely on shell prompt activation. Use uv run python script.py or configure your editor to point directly to .venv/bin/python. uv run guarantees the correct environment is active every time.

2. Hand-editing uv.lock

The Trap: Manually editing versions or hashes in uv.lock.
The Fix: Treat uv.lock like a compiled binary. Modify requirements in pyproject.toml or run uv add / uv remove, and let uv lock regenerate the lockfile. Commit uv.lock to Git so all team members and CI runners build identical environments.

3. Polluting the system Python with sudo pip

The Trap: Running sudo pip install <package>, which corrupts Linux distribution packages (/usr/lib/python3/dist-packages) and can break system utilities.
The Fix: Modern Linux distros trigger PEP 668 (externally-managed-environment) error. Use uv tool install <cli> for global CLI binaries (e.g. uv tool install ruff), and uv venv or uv run for code dependencies.


Exercises

  1. Run uv python list in your terminal and determine which CPython versions are currently installed on your workstation.
  2. Create a single-file script hash_tool.py using PEP 723 inline script metadata that depends on cryptography>=44.0.0 and prints the version of the installed cryptography library using uv run hash_tool.py.
  3. In an empty directory, initialize a project with uv init, add pytest as a development dependency (uv add --dev pytest), and verify that pyproject.toml differentiates runtime dependencies from [dependency-groups] dev.
  4. Inspect the generated uv.lock file in your text editor. Locate the exact wheel URL and SHA-256 hash that uv locked for your dependency.

Further reading

  • Official Documentation: uv — An extremely fast Python package and project manager (https://docs.astral.sh/uv/).
  • PEP 723: Inline Script Metadata (Brett Cannon, Ofek Lev).
  • PEP 621: Storing project metadata in pyproject.toml.
  • PEP 668: Marking Python base environments as “externally managed”.