You run uv init, and in under a second you have a complete project scaffolded — .gitignore, Python version pinning, a project config file, and your first script ready to run. No manual folder creation. No hunting for the right config format. No forgotten pip install.
In this hands-on guide, you will build a Python project from zero using uv — the Rust-powered tool that replaces pip, virtualenv, pyenv, and poetry in a single 2MB binary. Every command is real. Every file is explained.
In this tutorial, you will learn to:
- Scaffold a complete Python project with
uv init - Understand every file it creates and why each one matters
- Run scripts with
uv run— no manual environment activation - Manage and switch Python versions with
uv python - Migrate an existing
requirements.txtproject to uv - Bootstrap a working FastAPI app end-to-end
Page Contents
What Does uv init Actually Create?
Fire up your terminal and run:
uv init my-project
cd my-project
ls -la
uv creates exactly four files. Here is what each one does.
The Scaffolded Files
my-project/
├── .gitignore # Keeps .venv/, __pycache__, .env out of git
├── .python-version # Pins the exact Python runtime version
├── pyproject.toml # Project metadata + dependency declarations
└── README.md # Auto-generated placeholder
Note: Run
uv init --helpto see all options:--no-readme,--no-git,--package(creates an installable library project instead of a script), and--nameto override the project name.
uv does not create a uv.lock file at this stage — the lockfile is generated when you first run uv sync or uv lock.
Project Structure Deep Dive
Let us walk through each file in detail — what it contains, why it exists, and how uv uses it. Every file has a purpose, and understanding them individually makes you far more effective when things go wrong or when you need to customise the scaffold.
pyproject.toml — Your Project in One File
Open pyproject.toml. It looks like this out of the box:
[project]
name = "my-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
This replaces the old stack of setup.py, requirements.txt, and MANIFEST.in. Everything lives in one file following PEP 621.
The key fields:
name— used when installing the package (uv pip install my-project)version— follows Semantic Versioningrequires-python— declares the minimum Python version, used by pip and index serversdependencies— your runtime packages. Add them withuv add <package>
You can also add a [dependency-groups] section for dev-only tools (pytest, ruff):
[dependency-groups]
dev = ["pytest", "ruff"]
.python-version — Pin Your Runtime
This single line file pins the Python interpreter version:
3.12
When you run uv sync or uv run, uv reads this file and automatically provisions the correct Python version. No pyenv, no python3.12 path hunting.
.venv/ — Your Isolated Environment
uv creates the .venv/ directory automatically on first run. It is your project-specific Python environment — completely isolated from system Python and other projects.
.venv/
├── bin/ # Unix: python, pip, pytest executables
│ # Windows: Scripts/python.exe, Scripts/pip.exe
├── lib/
│ └── python3.12/
│ └── site-packages/ # All installed packages land here
└── pyvenv.cfg # Points to the managed Python interpreter
Key difference from venv: uv’s
.venv/is created in milliseconds, not seconds. Anduv runmeans you never need tosource .venv/bin/activate.
uv.lock — The Cross-Platform Lockfile
The uv.lock file is created when you first run uv sync or uv lock. This is a complete, resolved dependency graph — every package pinned to an exact version, with the hashes to verify integrity.
[[package]]
name = "fastapi"
version = "0.115.0"
source = { registry = "https://pypi.org/simple" }
sdist = { hash = "sha256:abc123..." }
wheel = [{ url = "...", hash = "sha256:def456..." }]
Always commit uv.lock to version control. When your colleague clones the repo and runs uv sync, they get bit-for-bit identical environments — across macOS, Windows, and Linux. This is the end of “works on my machine.”
Lockfile vs requirements.txt: A
requirements.txtpins top-level packages only.uv.locklocks every single transitive dependency — sub-dependencies, sub-sub-dependencies, and all.
Running Your First Script — uv run main.py
uv’s killer convenience: you never manually manage environments. On first run, it creates .venv/, syncs dependencies, and executes — all in one command.
uv run main.py
Hello from my-project!
Python 3.12.3
On a fresh machine with no .venv/ present, this command:
- Reads
.python-version→ installs Python 3.12 if missing - Creates
.venv/ - Runs
uv lockto resolve and pin dependencies - Runs
uv syncto install everything - Executes
main.pyinside the environment
All of that happens automatically. You just wait for the output.
Managing Python Versions
uv bundles its own Python distribution management. No pyenv, no python-build, no manual downloads.
Installing a New Python Version
# Install Python 3.12 (if not already present)
uv python install 3.12
# Install multiple versions at once
uv python install 3.11 3.12 3.13
Listing Installed Versions
uv python list
cpython-3.12.3+freethreaded-linux-x86_64-gnu <-- currently pinned
cpython-3.13.1-linux-x86_64-gnu
cpython-3.11.9-linux-x86_64-gnu
Switching Between Versions
# Pin a specific version for this project
uv python pin 3.11
# Verify .python-version was updated
cat .python-version
3.11
Automatic discovery: If
.python-versionsays3.12but only3.11is installed,uv syncprompts you to install the right version. No manual intervention needed.
Migrating Existing Projects
You have a project with a requirements.txt. Migrating takes two commands.
From requirements.txt
# Navigate to your existing project
cd my-old-project
# Run uv init (preserves existing files in the directory)
uv init
# Import all packages from requirements.txt into pyproject.toml
uv add -r requirements.txt
This reads requirements.txt, resolves every version, writes uv.lock, and installs everything — all in one step.
From Poetry
If your project uses Poetry, export your dependencies and import them with uv:
uv init
poetry export -f requirements.txt --without-hashes -o /tmp/req.txt
uv add -r /tmp/req.txt
Tip: The official Astral migration guide covers Poetry, pip-tools, pipenv, and conda — with exact commands for each.
Practical Mini-Project: A FastAPI App with uv
Let us tie everything together. You will build a real FastAPI application using only uv commands — from scaffold to running server.
Step 1: Scaffold the Project
uv init tasks-api
cd tasks-api
Step 2: Add FastAPI and Uvicorn
# Add FastAPI as a runtime dependency
uv add fastapi uvicorn
# Add pytest as a dev dependency
uv add --dev pytest httpx
Check pyproject.toml now:
[project]
name = "tasks-api"
version = "0.1.0"
description = "A simple task API built with FastAPI"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn>=0.32.0",
]
[dependency-groups]
dev = [
"pytest>=8.0.0",
"httpx>=0.28.0",
]
Step 3: Write the API
Replace main.py with this:
from fastapi import FastAPI, HTTPException
app = FastAPI(title="Tasks API", version="0.1.0")
tasks = []
@app.get("/")
def root():
return {"message": "Tasks API is running", "task_count": len(tasks)}
@app.get("/tasks")
def list_tasks():
return tasks
@app.post("/tasks")
def create_task(task: dict):
tasks.append(task)
return {"added": task}
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
if 0 <= task_id < len(tasks):
removed = tasks.pop(task_id)
return {"deleted": removed}
raise HTTPException(status_code=404, detail="Task not found")
Step 4: Run the Server
uv run uvicorn main:app --reload --port 8000
INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Application startup complete.
Step 5: Test It
curl -X POST http://127.0.0.1:8000/tasks
-H "Content-Type: application/json"
-d '{"title": "Finish uv tutorial", "priority": "high"}'
{"added": {"title": "Finish uv tutorial", "priority": "high"}}
curl http://127.0.0.1:8000/tasks
[{"title": "Finish uv tutorial", "priority": "high"}]
Add a tests/test_tasks.py file to make testing reproducible:
import pytest
from httpx import AsyncClient, ASGITransport
from main import app
@pytest.mark.asyncio
async def test_create_and_list_task():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post("/tasks", json={"title": "Test task"})
assert response.status_code == 200
assert response.json()["added"]["title"] == "Test task"
response = await client.get("/tasks")
assert response.status_code == 200
assert len(response.json()) == 1
uv run pytest tests/test_tasks.py
Run uv run pytest whenever you add or change endpoints — it uses the same environment uv sync set up, so no manual activation needed.
Step 6: Share the Project
Your collaborator clones the repo and runs one command:
uv sync # Reads uv.lock, creates .venv, installs everything
uv run uvicorn main:app --reload
Zero manual setup. Exact same versions. Works identically on their machine.
Common Mistakes and Gotchas
- Do not commit
.venv/to git. It is in.gitignoreby default — keep it that way. - Do not edit
uv.lockmanually. It is machine-generated. Editpyproject.tomlinstead and runuv lock --upgrade. uv runvsuv sync:uv runis for executing scripts.uv syncis for setting up an environment for development or deployment.- Python version mismatch: If you push
.python-version = 3.13but your CI only has 3.12, the run will fail. Always runuv python installin CI beforeuv sync.
Summary and Next Steps
In this tutorial you learned how uv scaffolds a complete Python project with four files, manages Python versions without pyenv, runs scripts without environment activation, and locks every dependency for reproducible environments.
You bootstrapped a working FastAPI app in under ten commands — and it will work identically on any machine that clones the repo. If you are deciding between FastAPI, Flask, and Django for your next API project, here is a full comparison of Python API frameworks to help you choose.
To go further with uv:
- Read the official uv documentation — the definitive reference for every command
- Check out our uv Python Package Manager guide for the broader picture — benchmarks, Docker integration, and the full pip-to-uv migration cheatsheet
- Explore GitHub Actions with uv for fast CI/CD pipelines
- Try building REST APIs with FastAPI — you already have the project scaffold

