<- back

# Switching to uv for Python Development

Posted on Oct 10, 2026

Python packaging has always felt like juggling too many tools. If you've worked on a Python project for more than a few days, you've probably used pyenv to switch Python versions, venv to create environments, pip to install packages, pip-tools or Poetry to lock them, and pipx to run global CLI tools.

It works, but having five different tools just to set up a basic project gets messy quickly.

Recently I've been using uv, a Python package and project manager built by Astral (the team behind Ruff). It's written in Rust, it's fast, and most importantly, it pulls all of these separate workflows into a single CLI tool.

## The Old Way vs The uv Way

A typical Python project setup usually looks something like this:

# Create and activate an environment
python -m venv .venv
source .venv/bin/activate  # or .venv\Scripts\activate on Windows

# Install packages
pip install requests pytest

# Dump installed packages (often includes messy unpinned transitive deps)
pip freeze > requirements.txt

With uv, the project workflow is much cleaner:

# Initialise a new project
uv init my-project
cd my-project

# Add dependencies
uv add requests
uv add --dev pytest

# Run your code
uv run python main.py

The biggest quality-of-life improvement here is uv run. You don't have to manually activate the virtualenv every time you open a terminal. uv checks that your environment matches your dependencies, syncs anything missing, and runs the script inside the environment automatically.

## Managing Python Versions Directly

Normally, if a project needs Python 3.11 and another needs Python 3.12, you have to manage that through pyenv, Homebrew, or system installers.

uv can install and manage Python interpreters directly without any extra setup:

# Download and install Python 3.12
uv python install 3.12

# Pin this project to 3.12 (writes a .python-version file)
uv python pin 3.12

Now the required Python version is explicitly tracked in the repo rather than being an assumption about whatever Python version happens to be on your machine.

## Lockfiles That Actually Work

In a uv project, your direct dependencies live in pyproject.toml:

[project]
name = "my-project"
version = "0.1.0"
dependencies = [
    "requests>=2.31",
]

Alongside it, uv generates a uv.lock file with the exact resolved dependency graph across all platforms. Whenever someone else clones the repo, they run:

uv sync

And their environment is instantly identical to yours. No missing sub-dependencies, no surprise version bumps breaking things in CI.

## Running CLI Tools with uvx

We often need standalone developer tools like Ruff, Black, or HTTPie. If you install them into your global Python environment, you risk polluting dependencies. If you use pipx, that's yet another tool to install.

uv comes with uvx (an alias for uv tool run), which runs a tool in a throwaway isolated environment:

# Run Ruff without installing it permanently
uvx ruff check .

# Or install it globally if you use it all the time
uv tool install ruff

## Running Standalone Scripts with Inline Dependencies

Sometimes you just want to write a quick 20-line script to scrape a page or parse an API response. Setting up a full project directory and virtual environment for a scratch script is overkill.

With uv, you can declare inline script metadata (PEP 723) right at the top of the file:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "requests",
# ]
# ///
import requests

response = requests.get("https://httpbin.org/get", timeout=10)
print(response.status_code)

Then run it directly:

uv run script.py

uv resolves the dependency, spins up an isolated cache environment, runs the script, and handles everything behind the scenes.

## You Don't Have to Rewrite Existing Projects

You don't need to migrate your existing projects all at once. uv also acts as a drop-in replacement for pip and venv:

# Create a venv
uv venv

# Install packages into the active venv
uv pip install requests

# Compile a requirements.txt
uv pip compile requirements.in -o requirements.txt

And if you want to convert a project that uses a requirements.txt file to uv:

uv add -r requirements.txt

## Final Thoughts

The speed of uv is nice and installing packages in milliseconds instead of watching pip download wheels is great. But the real win is simplicity. Having Python versioning, virtual environments, dependency locking, and script execution in one tool eliminates a lot of everyday friction.

If your current Python setup works well for you, there's no urgent need to rewrite everything. But for new projects or one-off scripts, uv has become my default choice.

You can check out the official docs at docs.astral.sh/uv.