Modern Python Workflow with Rye and uv

· 6 min read · Updated March 23, 2026 · intermediate
python uv rye packaging tooling

The Problem With the Old Way

Managing Python projects has always meant juggling multiple tools: pip for packages, venv or virtualenv for isolation, pyenv for Python versions, pip-tools for locking, and pipx for global tools. Each one has its own conventions, config formats, and sharp edges.

Two tools from Astral — the team behind Ruff — aim to collapse this stack.

What Astral’s Tools Do

Astral makes developer tools for Python. Their first entry was Ruff, a blazing-fast linter written in Rust. Then came two tools for project and package management:

  • Rye — a Python project manager launched in late 2023
  • uv — a Rust-based all-in-one tool that replaces pip, venv, pip-tools, pipx, and more

Important: Rye was officially deprecated in February 2025. The Astral team now recommends uv for all new work. If you’re coming from Rye, skip to the migration section below.

Why uv?

uv is fast — 10 to 100 times faster than pip for installing packages. It handles:

  • Installing and switching between Python versions
  • Creating and managing virtual environments
  • Installing packages from PyPI
  • Locking dependencies for reproducible installs
  • Running one-off scripts without a project
  • Managing global tools (like ruff or black)

One binary, one lockfile, one workflow.

Installing uv

# Official installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or via pip
pip install uv

# macOS with Homebrew
brew install uv

That’s it. No profile editing required on most systems.

Managing Python Versions

uv ships its own Python distribution finder and installer. No pyenv needed.

# Install a specific version
uv python install 3.12

# Install multiple at once
uv python install 3.12 3.11 3.10

# See what's installed
uv python list

# Find where a version is located
uv python find 3.12

Output of uv python list:

cpython-3.12.4-macos-arm64     /root/.local/share/uv/python/cpython-3.12.4-macos-arm64/lib/python3.12
cpython-3.11.9-macos-arm64     /root/.local/share/uv/python/cpython-3.11.9-macos-arm64/lib/python3.11

uv python install downloads Python versions on demand. You can also let uv run trigger the download automatically when you first run a script.

Starting a New Project

uv init my-project
cd my-project

This creates three things:

  • pyproject.toml — your project manifest
  • .python-version — pins the Python version for this project
  • uv.lock — reproducible dependency lockfile

Your pyproject.toml looks like this:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

Adding Dependencies

# Add a runtime dependency
uv add requests

# Add a dev dependency
uv add --dev pytest

# Add with a version constraint
uv add "httpx>=0.27"

# Add an optional dependency group
uv add "pandas" --optional analysis

These update both pyproject.toml and uv.lock in one step.

Optional dependency groups are defined in pyproject.toml as:

[project.optional-dependencies]
analysis = ["pandas"]

Installing and Locking

# Sync dependencies from lockfile (installs everything)
uv sync

# Update all dependencies to latest
uv add httpx --upgrade

# Install from a frozen lockfile (CI/production)
uv sync --locked

The uv.lock file should be committed to version control. It ensures every environment — local, CI, production — resolves the same package versions.

Running Code

In a project

# Run a script in the project environment
uv run python main.py

# Run with a specific Python version
uv run --python 3.11 python main.py

# Run pytest
uv run pytest

uv automatically creates a virtual environment on first uv run. You never manually source .venv/bin/activate.

Without a project

# Run a one-off script, installing dependencies on demand
uv run --with pandas python -c "import pandas as pd; print(pd.__version__)"

Output: 2.2.3

This downloads pandas, creates a temporary venv, runs the script, and cleans up — no pip install needed.

Running global tools

# Like pipx or npx — run a tool without polluting your global environment
uvx ruff check .
uvx black --check .
uvx httpx

Managing Global Tools

# Install a tool globally (persists after the command exits)
uv tool install ruff

# Run the installed tool
ruff check .

# Upgrade a tool
uv tool upgrade ruff

# List installed tools
uv tool list

Migrating from pip/venv

If you’re used to the old workflow:

# Old way:
python -m venv .venv
source .venv/bin/activate
pip install requests
pip freeze > requirements.txt

# uv way:
uv venv
uv add requests
# uv.lock is auto-generated

The lockfile is the key difference. With pip, requirements.txt is a snapshot. With uv, uv.lock is a precise dependency graph that uv uses to verify and install.

Migrating from Rye

Rye projects work with uv with minimal changes. The pyproject.toml format is compatible.

# 1. Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. Navigate to your existing Rye project
cd my-rye-project

# 3. Run uv sync — it reads your existing pyproject.toml
uv sync

# 4. Commit the new lockfile
git add uv.lock && git commit -m "Add uv.lock"

Rye projects using [[tool.rye.scripts]] need minor adjustments. In TOML, scripts that take arguments should use array syntax:

# pyproject.toml (Rye-style scripts — uv compatible)
[tool.rye.scripts]
dev = ["pytest", "tests/"]
server = ["python", "-m", "myapp.server"]
# Rye:  rye run dev
# uv:   uv run dev

Scripts defined under [project.scripts] also work:

[project.scripts]
myapp = "myapp.cli:main"
uv run myapp

A Complete Workflow Example

# 1. Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. Create a new project
uv init api-project && cd api-project

# 3. Pin Python version
uv python install 3.12

# 4. Add dependencies
uv add fastapi uvicorn httpx
uv add --dev pytest pytest-asyncio

# 5. Write some code
cat > main.py << 'EOF'
from fastapi import FastAPI
import httpx

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app)
EOF

# 6. Run the app
uv run python main.py

Output:

INFO:     Uvicorn running on http://127.0.0.1:8000

Key Differences From pip

pipuv
SpeedSlow10–100x faster
Lockfilerequirements.txt (manual)uv.lock (auto)
Python version managerNone (needs pyenv)Built-in
Virtual environmentManual venv + activateAuto (uv run)
Global toolspipxuvx
Dependency formatsrequirements.txt, setup.pypyproject.toml

Common Pitfalls

Not committing uv.lock. This defeats reproducibility. Treat it like package-lock.json in npm.

Mixing pip install and uv add. If you use both, the lockfile gets out of sync. Pick one tool and stick with it.

Running uv run on first use takes longer. uv creates the venv on demand. Subsequent runs use the cached environment and are fast.

Lockfile drift. If someone on the team runs pip install instead of uv add, the lockfile becomes stale. Run uv sync to realign.

Python on Windows. uv python install downloads official Python installers. On Windows, this may require administrator privileges — use the Windows Store Python integration or an admin shell if you hit permissions issues.

See Also