uv — Part 2: Tools, Advanced Workflows, and Best Practices#
This continues from Part 1. You should already know uv venv, uv pip install, uv init, and uv add.
requirements.txt vs pyproject.toml — when to use which#
| Situation | Use |
|---|---|
| Working on an old project or cloning from GitHub | requirements.txt |
Your deployment platform expects requirements.txt | requirements.txt |
| Starting a new project | pyproject.toml + uv.lock |
| Want reproducible, locked environments | pyproject.toml + uv.lock |
| May eventually publish as a package | pyproject.toml |
For learning: understand requirements.txt to work with existing projects. Use pyproject.toml for everything you build yourself.
Migrating an old project to uv#
git clone <repo-url>
cd repo
# Install old-style dependencies
uv venv
uv pip install -r requirements.txt
# Confirm it works
uv run python main.py
# If you want to modernize it
uv init # creates pyproject.toml in current folder
uv add requests pandas # re-add your key dependenciesFor platforms that still need requirements.txt (like some cloud hosts), generate one from pyproject.toml:
uv pip compile pyproject.toml -o requirements.txtuvx — run tools without installing them#
Sometimes you just want to run a CLI tool once without adding it to your project.
uvx ruff check .
uvx black .
uvx httpie GET https://httpbin.org/getuvx is shorthand for uv tool run. It downloads the tool, runs it in an isolated temporary environment, and leaves your project unchanged.
Run a specific version:
uvx [email protected] check .
uvx ruff@latest check .When to use uvx vs uv add --dev:
One-time check or formatting run? → uvx ruff check .
Tool is part of your daily workflow? → uv add --dev ruff
uv run ruff check .uv tool install — global tools#
If you want a tool available system-wide (not just in one project):
uv tool install ruff # install globally
ruff check . # run it directly from anywhere
uv tool list # see what's installed
uv tool upgrade --all # update all tools
uv tool uninstall ruff # remove itGood candidates for global tool install:
ruff - linter/formatter used across all Python projects
httpie - HTTP client for API testing
pre-commit - Git hooks manager
cookiecutter - project template generatorBuilding and publishing a Python package#
If you want to create a reusable package (not just a script or app):
uv init --package my-package
cd my-packageProject structure:
my-package/
├── pyproject.toml
├── README.md
└── src/
└── my_package/
└── __init__.pyBuild it:
uv buildThis creates:
dist/
├── my_package-0.1.0.tar.gz
└── my_package-0.1.0-py3-none-any.whlPublish to PyPI:
uv publishOnly do this when you understand package publishing, versioning, and PyPI credentials.
Common real-world workflows#
Quick data analysis script:
cat > analysis.py << 'PY'
import pandas as pd
df = pd.DataFrame({"score": [85, 91, 78]})
print(df.describe())
PY
uv add --script analysis.py pandas
uv run analysis.pyClone and run an old GitHub project:
git clone <repo-url>
cd repo
uv venv
uv pip install -r requirements.txt
uv run python main.pyNew FastAPI backend:
uv init api
cd api
uv add fastapi uvicorn
uv add --dev pytest ruff
uv run uvicorn main:app --reloadRun tests and lint:
uv run pytest
uv run ruff check .
uv run ruff format .What to commit and what not to#
✅ Commit:
pyproject.toml → your dependency declarations
uv.lock → exact pinned versions for reproducibility
.python-version → pins Python version for the project
src/ → your actual code
tests/ → your tests
❌ Don't commit:
.venv/ → large, auto-regenerated
__pycache__/ → auto-generated bytecode
.pytest_cache/ → test cache
dist/ → build output.gitignore template:
.venv/
__pycache__/
.pytest_cache/
.ruff_cache/
dist/
*.egg-info/The three modes of uv#
flowchart TD
A[What are you doing?] --> B{Mode}
B --> C[Single script]
B --> D[Full project]
B --> E[CLI tool]
C --> C1["uv add --script file.py requests"]
C1 --> C2["uv run file.py"]
D --> D1["uv init my-app"]
D1 --> D2["uv add requests"]
D2 --> D3["uv run main.py"]
D3 --> D4["uv lock + uv sync"]
E --> E1["uvx ruff check . (one-time)"]
E --> E2["uv tool install ruff (permanent)"]Daily command reference#
# Check
uv --version
# Python versions
uv python install 3.12
uv python list
uv run --python 3.12 python --version
# Virtual environment
uv venv
source .venv/bin/activate
deactivate
# Old-style project (requirements.txt)
uv pip install requests
uv pip install -r requirements.txt
uv pip freeze > requirements.txt
uv pip sync requirements.txt
# Modern project (pyproject.toml)
uv init my-app
uv add requests
uv add --dev pytest ruff
uv remove requests
uv run main.py
uv run pytest
uv tree
uv lock
uv sync
# Single-file script
uv add --script script.py requests
uv run script.py
# CLI tools
uvx ruff check .
uv tool install ruff
uv tool list
uv tool uninstall ruffComplete example: student grade API#
uv init grade-api
cd grade-api
uv add fastapi uvicorn
uv add --dev pytest ruffmain.py:
from fastapi import FastAPI
app = FastAPI()
def calculate_grade(marks: int) -> str:
if marks >= 90:
return "A"
elif marks >= 75:
return "B"
else:
return "C"
@app.get("/grade/{marks}")
def get_grade(marks: int):
return {"marks": marks, "grade": calculate_grade(marks)}tests/test_main.py:
from main import calculate_grade
def test_a():
assert calculate_grade(95) == "A"
def test_b():
assert calculate_grade(80) == "B"
def test_c():
assert calculate_grade(60) == "C"Run:
uv run uvicorn main:app --reload # start the API
uv run pytest # run tests
uv run ruff check . # lint
uv run ruff format . # formatVisit http://localhost:8000/grade/85 — returns {"marks": 85, "grade": "B"}.
Important Q&A#
Q: When should I use uvx vs uv add --dev?
A: uvx is for one-off tool runs you don’t want recorded in your project. uv add --dev ruff is for tools that are part of your project’s workflow — they get saved in pyproject.toml so every contributor uses the same version.
Q: Why does uv lock matter if pyproject.toml already lists versions?
A: pyproject.toml declares version ranges (e.g., requests>=2.0). uv.lock records the exact version that was resolved (e.g., requests==2.32.1). Without the lockfile, two people running uv sync at different times might get different package versions.
Q: How do I install a tool globally so I can run it from any folder?
A: uv tool install ruff installs ruff globally. It lives in its own isolated environment but its binary is available anywhere. Run uv tool list to see all globally installed tools.
Revision Checklist#
[ ] I know when to use requirements.txt vs pyproject.toml.
[ ] I can use uvx to run a tool without adding it to my project.
[ ] I can install global tools with uv tool install.
[ ] I know what to commit to Git: pyproject.toml and uv.lock.
[ ] I know what NOT to commit: .venv/ and __pycache__/.
[ ] I understand the three modes: script, project, and tool.
[ ] I can run tests, linting, and formatting all through uv run.