Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~28 min· topic 6 of 9

Topic 6.6

Modern Project Tooling with uv

In one line

uv is one fast tool that replaces pip, venv, pyenv and pipx: it creates projects, installs the right Python, adds dependencies, writes an exact uv.lock, and runs your code in the project's environment.

Think of it like this

A food-delivery app. Before it, you found a shop, bought ingredients, cooked and carried the meal yourself, with a different phone call for each step. The app does the whole chain with one tap. uv is that for Python projects: it picks the Python, builds the environment, installs the packages and runs the program with a single command each time.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

uv
A fast all-in-one Python project and package manager from Astral.
pyproject.toml
The project's settings file: name, version, Python needed, dependencies, tool settings.
uv.lock
A generated file listing the exact versions (with hashes) of every package the project uses.
Lock file
A file recording the exact result of dependency resolution, so everyone installs the same thing.
Resolution
Working out one set of package versions that satisfies every rule at once.
Sync
Make the installed environment match what the lock file says.
uvx
Run a command-line tool from PyPI in a temporary environment. Same as uv tool run.
.python-version
A one-line file naming the Python version the project should use.
Rust
A fast compiled language. uv is written in it, which is why it is so quick.

Step by step

01Install uv once

uv is a program, so you install it once per computer. The official installer is a one-line command from docs.astral.sh/uv. On macOS and Linux it downloads a script and runs it. On Windows you run a PowerShell command. Always copy the line from the official page, because running a script from the internet means trusting its source.

If you already have pip, pip install uv also works. Afterwards, uv --version prints a version number if the install worked. Close and reopen the terminal if the command is not found, so it reloads its PATH (Topic 6.5).

terminalwhole filebash
# macOS or Linux (copy the current line from docs.astral.sh/uv)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

uv --version

02Start a project

uv init creates the project files. The exact files it writes can differ a little between uv versions, but you can expect a pyproject.toml, a main.py, a README.md and a .python-version.

The pyproject.toml starts with an empty list of dependencies. Nothing is installed yet.

pyproject.tomlwhole filetoml
[project]
name = "weather-bot"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
terminal
$ uv init weather-bot
cd weather-bot

03Add dependencies

uv add edits pyproject.toml for you, creates the environment, resolves versions, writes uv.lock, and installs. After this command the dependencies list has your packages with a minimum version.

The packages go into .venv/ inside the project, the same lunchbox idea as in Topic 6.5, built automatically.

pyproject.tomlwhole filetoml
dependencies = [
    "httpx>=0.28",
    "pydantic>=2.12",
]
terminal
$ uv add httpx pydantic

04Run code with uv run

uv run main.py first makes sure .venv/ matches the lock, then runs your file with that environment. Do the same for any command: uv run python -c "import httpx", uv run pytest.

On the first run uv may print a few lines about creating or installing the environment before your program's own output. Your program's output follows as usual.

terminal
$ uv run main.py
── expected output ──
Hello from weather-bot!

05Lock and sync: reproducible environments

uv.lock is created and updated automatically when you add or remove packages. uv lock re-resolves; uv sync installs exactly what the lock says. A teammate or a CI machine clones the repository and runs uv sync, and gets the same packages you have.

Commit both pyproject.toml and uv.lock. Do not commit .venv/.

Lock and sync: reproducible environmentsdiagram
Rendering diagram…

06Python versions with uv

uv python install 3.13 fetches a Python build, and uv python pin 3.13 records the choice in .python-version. The environment uses a Python that satisfies both the pin and requires-python. This is the end of "install Python from a website, then hope it matches".

terminalwhole filebash
uv python install 3.13
uv python pin 3.13

07Tools: uvx and uv tool

To run a tool once without installing it into any project: uvx ruff check .. uv downloads ruff into a cache, runs it, and leaves your project untouched. To keep a tool on your PATH: uv tool install ruff.

Dev-only packages (testing, linting) belong in a dependency group, not in the runtime list (Topic 6.7): uv add --dev pytest.

terminalwhole filebash
uvx ruff check .
uv add --dev pytest

08Mapping the old tools to uv

If you know the old workflow, this is the translation. You do not have to relearn Python, only the commands around it.

cheat sheetwhole filebash
python -m venv .venv                 ->  uv venv   (or automatic with uv add / uv run)
pip install httpx                    ->  uv add httpx        (project)   or   uv pip install httpx
pip install -r requirements.txt      ->  uv sync             (project)   or   uv pip install -r requirements.txt
pyenv install 3.13                   ->  uv python install 3.13
pipx run ruff                        ->  uvx ruff
python main.py (after activating)    ->  uv run main.py

Try it yourself

  1. 1

    Predict the file

    In the first example, add a third entry to after's dependency list by hand ("rich>=13") and run it. This is exactly what uv add rich would do to the text, plus the install step.

  2. 2

    Try uv on your computer

    Install uv (first walkthrough step). Then run uv init demo, cd demo, uv add rich, and make main.py print something with from rich import print. Run it with uv run main.py. Delete the .venv folder and run it again: uv rebuilds it from uv.lock.

  3. 3

    Run a tool without installing

    Run uvx ruff --help in any folder. Nothing is added to a project. That is the "try a tool once" use of uvx.

Code & diagrams

Reading a pyproject.toml before and after uv add New tab

`uv` is a command-line program and cannot run in the browser, but the file it edits is plain TOML, and Python 3.11+ can read it with `tomllib`. The first text has the shape `uv init` writes (the details vary a little by uv version); the second is what `uv add httpx pydantic` changes. The version numbers in both are examples: uv writes whatever is newest when you run the command.

Sign in to run this example in your browser.

Expected output

after uv init
  name: weather-bot | python: >=3.13
  dependencies: []
after uv add httpx pydantic
  name: weather-bot | python: >=3.13
  dependencies: ['httpx>=0.28', 'pydantic>=2.12']
Wishes versus the lock (made-up version numbers) New tab

A model of why two files exist. `wishes` plays the part of `pyproject.toml`, the dictionary plays the part of `uv.lock`, and every version number here is invented for teaching.

Sign in to run this example in your browser.

Expected output

you asked for: ['httpx', 'pydantic']
the lock file pins: 4 packages
  anyio     4.9.0    pulled in by another package
  httpx     0.28.1   direct
  idna      3.10     pulled in by another package
  pydantic  2.12.0   direct
The everyday uv loopbash

None of these outputs are shown, because they depend on the uv version and on what has been downloaded. `&&` means "run the next command only if the first one worked", and `cd weather-bot` moves the terminal into the new project folder.

uv init weather-bot && cd weather-bot   # new project
uv add httpx                             # add a dependency
uv run main.py                           # run in the project's environment
uv add --dev pytest                      # a development-only dependency
uv run pytest                            # run the tests
uv sync                                  # on another machine: rebuild from uv.lock
The uv files in a projectdiagram
Rendering diagram…

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Edit pyproject.toml but forget to lock

Add a dependency to pyproject.toml by hand and run the program with uv run --frozen main.py.

pyproject.tomlwhole filetoml
dependencies = [
    "httpx>=0.28",
    "rich>=13",     # added by hand, not in uv.lock yet
]
terminal
$ uv run --frozen main.py
── what you'll see ──
ModuleNotFoundError: No module named 'rich'

Myth vs fact

Myth

uv is a different language or a replacement for Python.

Fact

It is a tool that manages Python projects and environments. Your code is ordinary Python and runs the same without it.

Myth

I must learn pip and venv first and can then forget them.

Fact

Knowing them helps because uv follows the same ideas and produces the same standard files and environments, and a lot of existing projects and tutorials still use them.

Myth

uv.lock is an optional generated mess.

Fact

It is the record that makes a project reproducible. Commit it.

Myth

uv run needs me to activate the venv first.

Fact

It does not. It uses the project's environment automatically.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    uv resolves dependencies for all platforms at once and stores that universal result in uv.lock, so a lock made on a Mac installs on Linux and Windows without re-resolving. That is a main reason to prefer it for teams and CI.

  • ▸

    In CI and Docker, install from the lock without changing it: use uv sync --locked (fail if the lock is out of date) or uv sync --frozen (use the lock as is). Both prevent a build from quietly picking up new versions.

  • ▸

    Because uv reads the standard pyproject.toml fields (PEP 621 for [project], PEP 735 for [dependency-groups]), a project that uses uv is still a normal Python project. Other tools can build it, and you are not locked in.

  • ▸

    uv sync is exact by default: it also removes packages from .venv that are not in the lock, and it installs the dev dependency group unless you pass --no-dev. For a production image, install without dev tools. uv pip install is the pip-compatible interface and does not touch pyproject.toml or uv.lock, so mixing it with uv add leaves the environment out of step with the lock. If a platform needs a plain requirements.txt (some deploy systems still do), uv export can write one from the lock.

  • ▸

    For a single script with no project folder, PEP 723 inline metadata puts the requirements in a comment block at the top of the file (# /// script, then # dependencies = ["requests"], then # ///), and uv run script.py builds a throw-away environment for it. It is a good way to share a small tool or an experiment as one file.

  • ▸

    uv is fast partly from a global cache and linking (hard links or copy-on-write where the file system allows it), so many environments share downloaded files. Creating a fresh environment for a throw-away experiment costs little, which makes "one environment per experiment" a realistic habit for AI work.

Remember this

  1. 1

    uv is a Python project and package manager written in Rust by Astral (the makers of Ruff). It is much faster than pip and covers jobs that used to need several tools: pip (install packages), venv (environments), pyenv (install and switch Python versions), pipx (run command-line tools in isolation) and pip-tools (lock files). Many new Python and AI projects now start with it. It is not part of Python itself: you install it once (the first walkthrough step shows how). Like everything in Topic 6.5, you type uv commands into a terminal.

  2. 2

    A project starts with uv init myproject, which makes a folder with a pyproject.toml (the project's description, Topic 6.7), a small main.py, a README.md, and a .python-version file. Then uv add httpx does three things at once: it adds httpx to [project] dependencies in pyproject.toml, creates .venv/ (the private folder of packages from Topic 6.5) if it does not exist, and installs the package into it. uv remove httpx undoes it.

  3. 3

    uv run main.py runs a command inside the project's environment, creating or updating the environment first if it is out of date. You never need to activate anything: uv run python, uv run pytest and uv run main.py always use the right Python and the right packages. This removes the "forgot to activate" and "installed in the wrong place" classes of bug.

  4. 4

    uv.lock is the key file. pyproject.toml records what you *asked for* (httpx>=0.28). uv.lock records what was *actually chosen*: every package, including indirect ones (packages that your packages need), at exact versions, with file hashes (a hash is a fingerprint of a file, Topic 6.9). Commit it, which means: save it into git with the rest of the project (see the Git course). uv sync makes .venv/ match the lock exactly, on any machine, and uv lock refreshes the lock from pyproject.toml. This is what makes "it works on my machine" true on every machine.

  5. 5

    uv also manages Python itself. uv python install 3.13 downloads a Python build and keeps it for uv's use; uv python pin 3.13 writes a .python-version file so the whole team uses the same one; requires-python in pyproject.toml states what the project supports. You no longer depend on whatever Python your operating system happens to ship.

  6. 6

    For tools rather than projects, uvx ruff check . (short for uv tool run) runs a command-line tool from PyPI in a temporary isolated environment, with nothing to install first; uv tool install ruff keeps it available everywhere. For older workflows, uv venv makes a bare venv and uv pip install -r requirements.txt is a faster drop-in for pip. Everything in the earlier topic (Topic 6.5) still applies: uv automates it.

Explain it without notes

01

What problems does uv solve, and which older tools does it replace?

02

What is the difference between pyproject.toml and uv.lock?

03

What does uv run do that plain python does not?

04

How do you make a teammate's environment match yours exactly?

Practice

01

Write the uv commands, one per line, to create a project called chatbot, move into its folder, add the package httpx, and run main.py.

02

In pyproject.toml, which section holds the packages needed at run time? Write a small [project] example with the name chatbot that needs httpx version 0.28 or newer.

03

Which files should be committed to git in a uv project, and which should not?

Trade-offs

  • ↔

    uv vs plain pip and venv. uv is faster, gives a real lock file and manages Python versions, but it is one more tool to install and learn, and some older workflows and documentation still assume pip. The standard files it uses mean you can switch back at any time.

  • ↔

    Lock everything vs leave ranges loose. A lock gives reproducibility at the cost of occasionally updating (uv lock --upgrade) to receive fixes. Loose ranges get fixes automatically but make builds change without a commit.

Done when you can

  • Done when you can start a project with uv init, add a package with uv add and run code with uv run.

  • Done when you can explain what uv.lock is and why it is committed.

  • Done when you can describe what uv sync does on a fresh clone.

  • Done when you can pin and install a Python version with uv.

  • Done when you can translate pip install, python -m venv, pyenv and pipx into uv commands.