Packages and Virtual Environments
The Problem
- Python ships with a standard library, but most real work needs third-party packages
- Installing packages globally causes dependency conflicts:
- Project A needs
requests==2.28; project B needsrequests==2.31 - Only one version can live in the global
site-packagesdirectory at a time
- Project A needs
- Solution: give each project its own isolated Python environment
uv
uvis a fast Python package and project manager written in Rust- Replaces
pip,pip-tools,virtualenv, and parts ofcondawith a single tool - Install once per machine:
curl -LsSf https://astral.sh/uv/install.sh | sh
- Verify:
uv --version
uv 0.4.18
Creating a Project
uv initcreates a new project in the current directory (or a named subdirectory)
uv init birdcount
cd birdcount
ls -a
.
..
.python-version
README.md
hello.py
pyproject.toml
uv initalso runsgit initand creates a.gitignore.python-versionpins the Python version for this project
pyproject.toml
pyproject.tomlis the standard way to describe a Python projectuv initgenerates a minimal version:
[project]
name = "birdcount"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[project]table holds metadatadependencieslists the packages this project needs (empty for now)
Adding Dependencies
- Use
uv addto add a package:
uv add requests
Resolved 5 packages in 234ms
Prepared 5 packages in 1.23s
Installed 5 packages in 89ms
+ certifi==2024.8.30
+ charset-normalizer==3.3.2
+ idna==3.10
+ requests==2.32.3
+ urllib3==2.2.3
uv addupdatespyproject.tomland creates (or updates)uv.lock
[project]
name = "birdcount"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.3",
]
The Lock File
uv.lockrecords the exact version of every installed package (including transitive dependencies)- Commit
uv.lockto version control so teammates get identical environments pyproject.tomlsays "I needrequests2.32 or newer";uv.locksays "userequests2.32.3 exactly"
Running Code
- Use
uv runto execute a script inside the project's environment:
import requests
response = requests.get("https://example.com/birds.json")
print(f"status: {response.status_code}")
uv run python fetch_birds.py
status: 200
uv runcreates the virtual environment in.venv/on the first run if it does not exist- Never activate the virtual environment manually:
uv runhandles it
How the Virtual Environment Works
.venv/contains a minimal Python installation and the project's installed packagesuv runprepends.venv/bintoPATHbefore executing the script- The Python interpreter and installed packages in
.venv/binshadow the system versions
- The Python interpreter and installed packages in
- This is the same mechanism described in the Shell Variables chapter
uv run python -c "import sys; print(sys.prefix)"
/Users/tut/birdcount/.venv
Reproducing an Environment
- A new collaborator clones the repository and runs:
uv sync
Resolved 5 packages in 12ms
Installed 5 packages in 341ms
+ certifi==2024.8.30
+ charset-normalizer==3.3.2
+ idna==3.10
+ requests==2.32.3
+ urllib3==2.2.3
uv syncreadsuv.lockand installs exactly those versions- No "works on my machine" surprises
Removing and Upgrading Dependencies
- Remove a package:
uv remove requests - Upgrade a package to the latest allowed version:
uv add --upgrade requests - Upgrade all packages:
uv lock --upgrade
Exercise: First Project
-
Create a new project called
weatherusinguv init. -
Add
httpxas a dependency and write a script that fetches any URL and prints its status code. -
Open
uv.lockand findhttpx. How many packages were installed to satisfy that one dependency? -
What is in
.venv/bin? Why is there more than just Python there?