Dependencies, Locking, and Updates
What You'll Learn
How to lock dependency versions so builds are reproducible, how to safely update packages, and how to use modern tools like pip-compile and uv.
Direct vs Transitive Dependencies
When you run pip install requests:
- Direct dependency:
requests(you chose this) - Transitive dependencies:
urllib3,certifi,charset-normalizer,idna(requests needs these)
pip freeze captures all of them — direct and transitive — with pinned versions:
certifi==2024.2.2
charset-normalizer==3.3.2
idna==3.6
requests==2.31.0
urllib3==2.2.1
This is good for reproducibility (exact same install on every machine) but bad for maintainability (you can't easily see which are yours vs. auto-pulled).
The Two-File Pattern
Separate what you want from what actually gets installed:
requirements.in ← YOUR choices (short, human-maintained)
requirements.txt ← LOCKED full list (generated, not edited by hand)
requirements.in:
requests>=2.28
flask>=3.0
python-dotenv
requirements-dev.in:
-r requirements.in
pytest>=8.0
ruff
mypy
pytest-cov
pip-compile — Lock Dependencies
pip install pip-tools
# Compile requirements.in → requirements.txt
pip-compile requirements.in
# Compile dev requirements
pip-compile requirements-dev.in -o requirements-dev.txt
# Upgrade all packages to latest compatible versions
pip-compile --upgrade requirements.in
# Upgrade a single package
pip-compile --upgrade-package requests requirements.in
Generated requirements.txt (managed by pip-compile, don't edit):
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
# pip-compile requirements.in
#
certifi==2024.2.2
# via requests
charset-normalizer==3.3.2
# via requests
idna==3.6
# via requests
requests==2.31.0
# via -r requirements.in
urllib3==2.2.1
# via requests
Install from the locked file:
pip-sync requirements.txt # installs exactly these, removes others
pip-sync requirements-dev.txt # for development
uv — Ultra-Fast Modern Package Manager
uv is the new generation tool — 10–100x faster than pip, with built-in locking:
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create venv and install
uv venv
source .venv/bin/activate
uv pip install requests flask
# Lock dependencies
uv pip freeze > requirements.txt
# Install from lockfile
uv pip install -r requirements.txt
# Sync environment to exact lockfile state
uv pip sync requirements.txt
Checking for Security Vulnerabilities
# Check for known vulnerabilities
pip install pip-audit
pip-audit
# With uv
uv pip audit
Example output:
Found 1 known vulnerability in 1 package:
Name Version ID Fix Versions
------ ------- ------------------- ------------
pillow 9.0.0 GHSA-4fx9-vc88-q2xc 9.0.1
Updating Packages Safely
Never just run pip install --upgrade-all blindly — it can break things. Instead:
# Check what's outdated
pip list --outdated
# Update one package at a time and test
pip install --upgrade requests
pytest tests/ # verify nothing broke
# With pip-compile: controlled upgrade
pip-compile --upgrade-package requests requirements.in
pip-sync requirements.txt
pytest tests/
git add requirements.txt requirements.in
git commit -m "chore: upgrade requests to 2.32.0"
Checking Dependency Conflicts
pip install pipdeptree
pipdeptree --warn all # show dependency tree with conflicts
pip check # check for broken requirements
Separating Environments by Purpose
| File | Purpose |
|---|---|
requirements.in | Your direct production deps |
requirements.txt | Locked production deps (generated) |
requirements-dev.in | Your direct dev deps |
requirements-dev.txt | Locked dev deps (generated) |
requirements-test.txt | CI/test-only deps |
In CI/CD, install only what's needed:
# Production deploy
pip-sync requirements.txt
# CI testing
pip-sync requirements-dev.txt
Workflow Summary
# Starting a new project
python3 -m venv venv
source venv/bin/activate
pip install pip-tools
# Add a dependency
echo "requests>=2.28" >> requirements.in
pip-compile requirements.in
pip-sync requirements.txt
git add requirements.in requirements.txt
# Weekly: check and upgrade
pip list --outdated
pip-compile --upgrade requirements.in
pip-sync requirements.txt
pytest tests/ # verify
git commit -m "chore: weekly dependency update"
# Security audit
pip-audit
Common Mistakes
| Mistake | Fix |
|---|---|
Editing requirements.txt by hand | Edit requirements.in, run pip-compile |
| No locked file, just loose version specs | Always lock for production |
| Upgrading all packages at once | Upgrade one at a time and test |
| No security audits | Run pip-audit regularly |
| Different package versions per developer | Commit the lockfile |
Quick Reference
# pip-tools
pip install pip-tools
pip-compile requirements.in # generate lockfile
pip-compile --upgrade requirements.in # upgrade all
pip-sync requirements.txt # install exact versions
# uv (faster alternative)
uv venv && source .venv/bin/activate
uv pip install requests
uv pip sync requirements.txt
# Security
pip install pip-audit
pip-audit
# Check state
pip list --outdated
pip check
pipdeptree