Overview
This guide outlines the standard process for initializing a clean, reproducible Python project using uv, the Rust-based package manager. It is optimized for open source projects where reproducibility, minimal setup friction, and a clean dependency graph matter.
uv replaces pip, venv, pip-tools, and pyenv with a single tool that is significantly faster than any of them.
Why uv
Before uv, a typical Python project setup required:
- Installing the right Python version manually or via
pyenv - Creating a virtual environment with
python -m venv .venv - Activating it with
source .venv/bin/activate - Installing packages with
pip install - Freezing dependencies into
requirements.txt
This process is fragile, slow, and produces lockfiles that are not platform-aware. uv replaces all of it with a single tool backed by a Rust resolver that handles environment creation, dependency locking, and script execution automatically.
Step 1: Scaffold the Project
Instead of manually creating directories and virtual environments, let uv handle the boilerplate.
uv init my_project
cd my_project
This single command:
- Creates the
my_project/directory - Generates a
pyproject.tomlwith project metadata - Creates a
hello.pyentry point - Pins the current Python version in
.python-version
The resulting structure is immediately ready for development and compatible with PyPI publishing conventions.
Step 2: Add Dependencies
Never use pip install in a uv-managed project. Always use uv add to ensure every dependency is tracked in pyproject.toml and reflected in the lockfile.
# Add a standard runtime package
uv add requests
# Add a development dependency (formatters, linters, test runners)
uv add --dev ruff
uv add --dev pytest
Running uv add does three things atomically:
- Creates
.venv/if it does not already exist - Installs the package into the environment
- Updates
uv.lockwith a pinned, platform-aware lockfile
The uv.lock file should be committed to version control. It guarantees that every contributor and every CI run installs the exact same dependency tree regardless of when the install happens.
Step 3: Run the Project
You do not need to activate the virtual environment manually. uv run handles the context automatically.
uv run hello.py
This resolves the environment from pyproject.toml, ensures all dependencies are installed, and executes the script in the correct context. There is no source .venv/bin/activate step.
For projects with a defined entry point, you can also run tools directly:
uv run pytest
uv run ruff check .
Step 4: Sync a Cloned Project
When a contributor clones the repository, they only need one command to get a fully working environment:
uv sync
This reads uv.lock, creates .venv/, and installs every dependency at the pinned versions. No manual steps required. This is the key advantage of uv over a plain requirements.txt workflow for open source projects.
Recommended pyproject.toml Structure
A clean starting point for an open source project:
[project]
name = "my-project"
version = "0.1.0"
description = "A short description of what this does."
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
authors = [{ name = "Your Name", email = "[email protected]" }]
dependencies = [
"requests>=2.32",
]
[project.urls]
Repository = "https://github.com/yourname/my-project"
[tool.uv]
dev-dependencies = [
"ruff>=0.4",
"pytest>=8.0",
]
Inline Scripting for Single-File Tools
For single-file scripts where a full project structure is unnecessary, use uv's inline script metadata format. This is useful for quick red team tools, automation scripts, or one-off utilities.
Add a metadata block to the top of the script file:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests",
# "colorama",
# ]
# ///
import requests
import colorama
print("Running...")
Then execute it directly from anywhere on the system:
uv run script.py
uv reads the metadata comment block, creates an ephemeral environment in the cache, installs the listed dependencies, runs the script, and discards the environment. No requirements.txt, no venv, no activation step.
This format is defined by PEP 723 and is the correct way to distribute portable Python scripts that have dependencies.
Common Commands Reference
# Initialize a new project
uv init my_project
# Add a runtime dependency
uv add requests
# Add a development dependency
uv add --dev pytest
# Run a script or tool
uv run hello.py
uv run pytest
# Sync environment from lockfile (after cloning)
uv sync
# Update all dependencies to their latest compatible versions
uv lock --upgrade
# Remove a dependency
uv remove requests
# Show installed packages
uv pip list
# Run an inline script with no project setup
uv run script.py
Project Structure After Setup
my_project/
├── .python-version # Pinned Python version
├── .venv/ # Managed automatically by uv
├── hello.py # Entry point
├── pyproject.toml # Project metadata and dependencies
├── uv.lock # Deterministic lockfile (commit this)
└── README.md
The .venv/ directory should be added to .gitignore. Everything else, including uv.lock, should be committed.