Skip to main content

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

FilePurpose
requirements.inYour direct production deps
requirements.txtLocked production deps (generated)
requirements-dev.inYour direct dev deps
requirements-dev.txtLocked dev deps (generated)
requirements-test.txtCI/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

MistakeFix
Editing requirements.txt by handEdit requirements.in, run pip-compile
No locked file, just loose version specsAlways lock for production
Upgrading all packages at onceUpgrade one at a time and test
No security auditsRun pip-audit regularly
Different package versions per developerCommit 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

What's Next

Lesson 3: Packaging Internal Tools