Dependency Management

Updated

September 8, 2026

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 — refresh uv.lock from pyproject.toml.
  • uv sync — make .venv match 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 sync

uv 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.py

Output:

{
  "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 httpx

uv add:

  1. Adds httpx to [project] dependencies in pyproject.toml.
  2. Resolves a concrete version and writes uv.lock.
  3. 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 dependency

Commit 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.py

menu.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.py

Output:

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.toml per project. requires-python = ">=3.14".
  • Runtime packages: uv add pkg. Tools: uv add --dev pkg.
  • Commit uv.lock. Install with uv sync.
  • Do not edit uv.lock by hand.
  • Do not pip install into a random interpreter.
  • Do not add a package the standard library already covers.

Try this

  1. Run uv add --dev pytest in a throwaway copy of the folder. Open pyproject.toml and find pytest under [dependency-groups].
  2. In menu.py, add a "tea": 2 entry and confirm json.dumps includes it.
  3. In bill.py, reject a negative price with ValueError (stdlib only — no new dependency).