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).
# 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 --version02Start 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.
[project]
name = "weather-bot"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []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.
dependencies = [
"httpx>=0.28",
"pydantic>=2.12",
]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.
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/.
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".
uv python install 3.13
uv python pin 3.1307Tools: 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.
uvx ruff check .
uv add --dev pytest08Mapping 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.
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.pyTry it yourself
- 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 whatuv add richwould do to the text, plus the install step. - 2
Try uv on your computer
Install uv (first walkthrough step). Then run
uv init demo,cd demo,uv add rich, and makemain.pyprint something withfrom rich import print. Run it withuv run main.py. Delete the.venvfolder and run it again: uv rebuilds it fromuv.lock. - 3
Run a tool without installing
Run
uvx ruff --helpin any folder. Nothing is added to a project. That is the "try a tool once" use ofuvx.
Code & diagrams
`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.
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']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.
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 directNone 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.lockBreak 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.
dependencies = [
"httpx>=0.28",
"rich>=13", # added by hand, not in uv.lock yet
]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) oruv sync --frozen(use the lock as is). Both prevent a build from quietly picking up new versions. - ▸
Because uv reads the standard
pyproject.tomlfields (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 syncis exact by default: it also removes packages from.venvthat are not in the lock, and it installs thedevdependency group unless you pass--no-dev. For a production image, install without dev tools.uv pip installis the pip-compatible interface and does not touchpyproject.tomloruv.lock, so mixing it withuv addleaves the environment out of step with the lock. If a platform needs a plainrequirements.txt(some deploy systems still do),uv exportcan 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# ///), anduv run script.pybuilds 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
uvis a Python project and package manager written in Rust by Astral (the makers of Ruff). It is much faster thanpipand 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) andpip-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
A project starts with
uv init myproject, which makes a folder with apyproject.toml(the project's description, Topic 6.7), a smallmain.py, aREADME.md, and a.python-versionfile. Thenuv add httpxdoes three things at once: it addshttpxto[project] dependenciesinpyproject.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 httpxundoes it. - 3
uv run main.pyruns 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 pytestanduv run main.pyalways use the right Python and the right packages. This removes the "forgot to activate" and "installed in the wrong place" classes of bug. - 4
uv.lockis the key file.pyproject.tomlrecords what you *asked for* (httpx>=0.28).uv.lockrecords 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 syncmakes.venv/match the lock exactly, on any machine, anduv lockrefreshes the lock frompyproject.toml. This is what makes "it works on my machine" true on every machine. - 5
uvalso manages Python itself.uv python install 3.13downloads a Python build and keeps it for uv's use;uv python pin 3.13writes a.python-versionfile so the whole team uses the same one;requires-pythoninpyproject.tomlstates what the project supports. You no longer depend on whatever Python your operating system happens to ship. - 6
For tools rather than projects,
uvx ruff check .(short foruv tool run) runs a command-line tool from PyPI in a temporary isolated environment, with nothing to install first;uv tool install ruffkeeps it available everywhere. For older workflows,uv venvmakes a bare venv anduv pip install -r requirements.txtis a faster drop-in forpip. Everything in the earlier topic (Topic 6.5) still applies:uvautomates it.
Explain it without notes
What problems does uv solve, and which older tools does it replace?
What is the difference between pyproject.toml and uv.lock?
What does uv run do that plain python does not?
How do you make a teammate's environment match yours exactly?
Practice
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.
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.
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 withuv addand run code withuv run.Done when you can explain what
uv.lockis and why it is committed.Done when you can describe what
uv syncdoes 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,pyenvandpipxinto uv commands.