Modern Toolchain: uv
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.pyOutput:
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.pyWhy 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 listInstall Python 3.14 directly into an unprivileged user directory:
uv python install 3.14Pin your project directory to use Python 3.14:
uv python pin 3.14This 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_appThis 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.pyOutput:
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
- Run
uv python listin your terminal and determine which CPython versions are currently installed on your workstation. - Create a single-file script
hash_tool.pyusing PEP 723 inline script metadata that depends oncryptography>=44.0.0and prints the version of the installed cryptography library usinguv run hash_tool.py. - In an empty directory, initialize a project with
uv init, addpytestas a development dependency (uv add --dev pytest), and verify thatpyproject.tomldifferentiates runtime dependencies from[dependency-groups] dev. - Inspect the generated
uv.lockfile in your text editor. Locate the exact wheel URL and SHA-256 hash thatuvlocked 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”.