Dependency Management
Dependency Management
A dependency is a package your project imports that did not come with Python. You write it down in pyproject.toml, lock exact versions in uv.lock, and install with uv sync. The boring default is still the standard library until a package earns its line.
Mental model
Three files, one job:
| File | Role |
|---|---|
pyproject.toml |
What you want (httpx, pytest) and which Python |
uv.lock |
Exact versions that worked, for every machine |
.venv/ |
The install of those versions on this machine |
Commands:
uv add pkg— add a runtime dependency, update the lockfile, install.uv add --dev pkg— add a tool (ruff,pytest) that the desk itself does not import.uv lock— refreshuv.lockfrompyproject.toml.uv sync— make.venvmatch the lockfile.
Programs in this chapter use only the standard library so they run offline. uv add is shown as a command you would run when you actually need a package.
Worked examples
Case 1: A complete pyproject.toml
Save as pyproject.toml in an empty folder. This is enough for a desk that only uses the stdlib:
# pyproject.toml
[project]
name = "desk"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = []
[dependency-groups]
dev = [
"pytest>=9.0",
"ruff>=0.16",
]dependencies = [] means runtime imports come from Python itself. The dev group is for tools. uv add --dev pytest writes that group for you if you would rather not type it.
Pin the interpreter next to it:
uv python pin 3.14
uv syncuv sync creates .venv if needed and installs the dev tools.
Case 2: Stdlib first (runs offline)
Save as menu.py. JSON is in the standard library. You do not uv add json.
# menu.py
import json
def main():
menu = {"soup": 6, "pie": 9}
text = json.dumps(menu, indent=2)
print(text)
loaded = json.loads(text)
print(loaded["pie"])
if __name__ == "__main__":
main()Run:
uv run python menu.pyOutput:
{
"soup": 6,
"pie": 9
}
9
That program does not need the network after Python is installed. Prefer this shape until a library is doing real work you do not want to write.
Case 3: uv add, uv lock, uv sync (documented)
When you do need a package — HTTP, a cloud SDK, a parser that is not in the stdlib — you do not edit dependencies by guess. You run:
uv add httpxuv add:
- Adds
httpxto[project] dependenciesinpyproject.toml. - Resolves a concrete version and writes
uv.lock. - Installs it into
.venv.
The dependencies list then looks like this (the version number will follow whatever uv add resolved):
dependencies = [
"httpx>=0.28",
]Related commands:
uv lock # refresh uv.lock from pyproject.toml
uv sync # install from uv.lock into .venv
uv add --dev ruff pytest
uv remove httpx # drop a runtime dependencyCommit both pyproject.toml and uv.lock. A teammate runs uv sync and gets the same versions.
This book’s runnable listings stay on the stdlib so you can work through them on a train. When a later chapter truly needs a package, it will say uv add and then import it.
Case 4: A lockfile is not a mystery novel
You do not edit uv.lock by hand. You read pyproject.toml. After uv add or a manual edit of dependencies, run:
uv lock
uv sync
uv run python menu.pymenu.py still prints the same output as Case 2. Adding a lockfile does not change a program that never imported the new package — which is a reason not to add packages “just in case.”
Save as bill.py and keep billing in the stdlib:
# bill.py
def line_total(qty, price):
return qty * price
def main():
print(line_total(2, 6))
if __name__ == "__main__":
main()Run:
uv run python bill.pyOutput:
12
The trap
pip install httpx into whatever python3 happens to be, with no pyproject.toml and no lockfile. It works on your laptop. It does not work on the next one.
The second trap is adding a package for work the standard library already does: json, pathlib, urllib.request, datetime, subprocess. Extra dependencies are extra supply chain, extra sync time, and extra version fights. Earn each line in dependencies.
The boring rule
- One
pyproject.tomlper project.requires-python = ">=3.14". - Runtime packages:
uv add pkg. Tools:uv add --dev pkg. - Commit
uv.lock. Install withuv sync. - Do not edit
uv.lockby hand. - Do not
pip installinto a random interpreter. - Do not add a package the standard library already covers.
Try this
- Run
uv add --dev pytestin a throwaway copy of the folder. Openpyproject.tomland findpytestunder[dependency-groups]. - In
menu.py, add a"tea": 2entry and confirmjson.dumpsincludes it. - In
bill.py, reject a negativepricewithValueError(stdlib only — no new dependency).