Dependency Management: From pip to the pyroject.toml to uv
Learn how to modernize your Python dependency management by building on traditional pip + venv
workflows and transitioning to modern tools like uv
for better organization and performance.
Learning Objectives
- Master pip + venv fundamentals that form the foundation of Python dependency management
- Organize dependencies using
pyproject.toml
for better structure and maintainability - Understand how simple requirements become complex dependency trees
- Introduce UV as a modern Python package manager built on solid foundations
- Create reproducible environments using lockfiles and dependency groups
Why This Matters for RAP
This workshop directly supports Silver RAP by teaching you to include comprehensive dependency information in your repository. You'll learn to structure dependencies using pyproject.toml
, which not only ensures reproducibility but also shapes your analytical pipeline into a proper package - a key step toward Gold RAP's "code is fully packaged" requirement.
Task 1: Understanding Traditional Python Dependency Management
Let's start by setting up a traditional Python environment to understand the current approach and its limitations.
1.1 Create a Virtual Environment
First, let's create a clean virtual environment using the standard venv
module:
Virtual Environment Basics
Virtual environments isolate your project dependencies from your system Python installation. The .venv
directory contains a complete Python installation specific to your project.
# Create a new virtual environment
python -m venv .venv
# Activate the virtual environment
source .venv/bin/activate
# Verify we're in the virtual environment
which python
1.2 Examine Current Dependencies
Let's look at what dependencies our project needs:
You should see a mix of dependencies including documentation tools, development tools, and core project dependencies.
1.3 Install Dependencies and Observe Complexity
Now let's install these dependencies and see what actually gets installed:
# Install all requirements
pip install -r requirements.txt
# See what was actually installed (this will be much longer!)
pip freeze
Dependency Explosion
Notice how our simple requirements file with ~10 packages resulted in many more installed packages. These are sub-dependencies (dependencies of dependencies) that pip resolved automatically.
Understanding Traditional Approach Limitations
The traditional pip + venv
approach works well for basic projects but has some challenges as projects grow:
- Mixed dependency purposes: Production, development, and documentation dependencies are all in one file
- Sub-dependency visibility:
pip freeze
shows all packages, making it hard to distinguish your direct dependencies - Slower resolution: pip can be slow with complex dependency trees
- No built-in lockfiles: Reproducible environments require manual
pip freeze
management
pip
and venv
is still valid
Don't worry - pip
and venv
is still a perfectly valid approach for many projects! We're building on this solid foundation, not replacing it entirely.
Task 2: Organizing Dependencies with pyproject.toml
Before we introduce uv
, let's improve our dependency organization using the modern pyproject.toml
standard.
2.1 Understanding pyproject.toml Structure
Complete pyproject.toml Guide
This section focuses on dependency management within pyproject.toml. For comprehensive coverage of project metadata, dynamic versioning, and tool configuration, see our Packaging with pyproject.toml workshop.
The pyproject.toml
file is the modern standard for Python project configuration. For detailed guidance on writing pyproject.toml files, see the official writing guide. Let's examine our current minimal setup:
2.2 Add Project Dependencies
Let's organize our dependencies by purpose. Open pyproject.toml
and add the following sections:
[project] # (1)!
name = "package-your-code-workshop"
version = "0.1.0"
description = "A workshop demonstrating Python packaging best practices"
dependencies = [ # (2)!
"pandas>=2.1.0",
"numpy>=1.25.0",
"matplotlib>=3.7.0",
"seaborn>=0.12.0",
"plotly>=5.15.0",
"oops_its_a_pipeline@git+https://github.com/nhsengland/oops-its-a-pipeline.git", # (3)!
"nhs_herbot@git+https://github.com/nhsengland/nhs_herbot.git",
]
[dependency-groups] # (4)!
docs = [ # (5)!
"mkdocs>=1.5.0",
"mkdocs-material>=9.0.0",
"mkdocstrings>=0.22.0",
"mkdocstrings-python>=1.0.0",
]
dev = [ # (6)!
"ruff>=0.4.0",
"pytest>=7.4.0",
]
[tool.setuptools.packages.find] # (7)!
include = ["practice_level_gp_appointments*"]
- Core project metadata section following PEP 621
- Core dependencies required for your application to run in production
- Git-based dependencies - packages installed directly from repositories
- Dependency groups for development tools following PEP 735
- Documentation generation dependencies - only needed when building docs
- Development tools - only needed when coding and testing
- Build tool configuration - tells setuptools which packages to include
Why Separate Groups?
Now you can install exactly what you need:
Dependency Groups: Modern Best Practice
We're using dependency-groups
as the modern best practice for development tools:
# Modern approach - dependency groups for dev tools
[dependency-groups]
dev = ["pytest", "ruff"]
docs = ["mkdocs", "mkdocs-material"]
Dependency groups are specifically designed for development tools, testing, and build processes. They're supported by modern tools like UV and newer versions of pip.
For backwards compatibility with older pip versions, you can still use:
2.3 Test the New Structure
Let's clean our environment and test our new dependency structure:
# Deactivate and remove the old environment
deactivate
rm -rf .venv
# Create a fresh environment
python -m venv .venv
source .venv/bin/activate
# Install just core dependencies
pip install -e .
# Test that our package is accessible
python -c "import practice_level_gp_appointments; print('Success')"
# Now install development tools too
pip install -e .[dev]
# Install everything
pip install -e .[dev,docs]
Previously encountered issues with libodbc.so.2
and pyodbc
If you encounter an error during this stage related to libodbc.so.2
, pyodbc
, or similar it might be some missing system dependencies. These should be installed automatically when you create your container but if you are still getting the error try the following commands:
sudo apt-get update
sudo apt-get install -y unixodbc unixodbc-dev
pip install --force-reinstall pyodbc
python -c "import practice_level_gp_appointments; print('Success')"
This should resolve the issue.
Task 3: Introducing UV
Now let's introduce UV, a modern Python package manager built in Rust that builds on the foundations we've established.
3.1 Install UV
Let's install UV on your system:
# Install UV (macOS/Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Restart your shell or source the new PATH
source ~/.bashrc # or ~/.zshrc depending on your shell # (1)!
# Verify installation
uv --version
- To check the type of shell you're using, run
echo $SHELL
. If it ends withzsh
, usesource ~/.zshrc
instead.
Windows Installation
On Windows, use: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
. For more installation options, see the UV installation guide.
3.2 Migrate Existing Project to UV
Let's migrate our existing project to use UV while keeping our pyproject.toml structure. For a comprehensive migration guide, see Migrating from pip to a UV project:
# First, clean the current environment
deactivate
rm -rf .venv
# Create a UV-managed virtual environment
uv venv
# Activate the environment
source .venv/bin/activate
# Install dependencies from pyproject.toml
uv sync --all-groups
UV Sync Command
uv sync
reads your pyproject.toml
and installs all dependencies. The --all-groups
flag includes all dependency groups (dev, docs, etc.).
3.3 Practice: Selective Installation with UV
Now let's practice using UV's dependency groups with the same optional-dependencies
syntax.
Practice Different Installation Patterns
Let's practice installing dependencies for different use cases using our existing pyproject.toml structure:
# Start with a clean slate
deactivate
rm -rf .venv
# 1. Core dependencies only (production-like)
uv venv
source .venv/bin/activate
uv sync
pip list # See what got installed
# 2. Add development tools
uv sync --group dev
pip list # Notice the additional packages
# 3. Clean and try docs only
deactivate
rm -rf .venv
uv venv
source .venv/bin/activate
uv sync --group docs
pip list # Just core + docs packages
# 4. Everything for full development
uv sync --all-groups
pip list # All packages
Real-World Scenarios
New Developer Setup: A developer working on code (not docs)
Documentation Writer: Someone updating docs (not coding)
Production Deployment: Server needs only core functionality
CI/CD Pipeline: Different jobs, different needs
3.4 Alternative: Building a Project from Scratch with UV
Let's also practice the "greenfield" approach - starting a completely new project with UV:
# Clean everything
deactivate
rm -rf .venv uv.lock
# Initialize a new UV project
uv init --name package-your-code-workshop --python 3.12
# Create and activate environment
uv venv --python 3.12
source .venv/bin/activate
# Add dependencies one by one (UV builds pyproject.toml automatically)
uv add pandas numpy matplotlib seaborn plotly
# Add development dependencies
uv add --group dev ruff pytest
# Add documentation dependencies
uv add --group docs mkdocs mkdocs-material mkdocstrings mkdocstrings-python
# Check what UV created
cat pyproject.toml
UV Auto-Generation
UV automatically creates and updates your pyproject.toml
as you add dependencies. This is great for new projects where you want to build up dependencies incrementally.
3.5 Understanding UV Lockfiles
UV automatically creates a uv.lock
file for reproducible builds. Let's explore it:
# Check if lockfile exists
ls -la uv.lock
# Look at the lockfile structure
head -20 uv.lock
# Install from exact lockfile versions
uv sync --frozen
Always Commit Lockfiles
Add uv.lock
to version control to ensure everyone gets exactly the same dependency versions. This is essential for RAP Gold standard reproducibility - your analytical pipelines will run identically across different environments and team members.
Task 4: Working with UV in Practice
Let's explore common UV workflows you'll use in daily development. For comprehensive guidance on UV project workflows, see the Working on Projects guide.
4.1 Adding and Removing Dependencies
# Add a new dependency
uv add requests
# Add a development dependency
uv add --group dev mypy
# Remove a dependency
uv remove requests
# Upgrade all dependencies
uv lock --upgrade
4.2 Managing Environments
# Create environment with specific Python version
uv venv --python 3.11
# List available Python versions
uv python list
# Install a specific Python version (if needed)
uv python install 3.11
4.3 Running Commands
# Run commands in the UV environment
uv run python --version
# Run the package as a module (uses __main__.py)
uv run python -m practice_level_gp_appointments
# Run a specific script file
uv run python practice_level_gp_appointments/pipeline.py
# Run tools from your environment
uv run ruff check .
UV Run
uv run
automatically activates the virtual environment and runs the command, even if you haven't manually activated the environment.
Migration Command Reference
Here's a quick reference for migrating from pip workflows to UV:
Traditional pip | Modern UV | Purpose |
---|---|---|
pip install package |
uv add package |
Add new dependency |
pip install -r requirements.txt |
uv sync |
Install all dependencies |
pip install -e . |
uv sync |
Install project in development mode |
pip freeze > requirements.txt |
uv export > requirements.txt |
Export current environment |
pip install --upgrade package |
uv add package --upgrade |
Upgrade package |
python script.py |
uv run python script.py |
Run Python script |
Command Details
Key differences to note:
uv sync
installs your project and dependencies frompyproject.toml
uv export
creates requirements.txt from the current environmentuv lock
updates the lockfile (separate from installation)- All UV commands automatically handle virtual environments
Best Practices
Dependency Group Organization
Keep It Simple: Two Groups
For most projects, you only need two dependency groups:
- Core dependencies (
dependencies
): What your app needs to run - Development dependencies (
dev
): Tools for coding, testing, linting
Simple, effective setup:
Installation:
Advanced: More Granular Groups
If your project grows complex, you can break down further:
docs
: Documentation generation toolstest
: Testing-specific dependencies (separate from general dev)typing
: Type checking tools (mypy, type stubs)jupyter
: Jupyter notebook dependencies
Example comprehensive setup:
[dependency-groups]
dev = ["ruff", "pytest"]
test = ["pytest", "pytest-cov"]
docs = ["mkdocs", "mkdocs-material"]
typing = ["mypy", "types-requests"]
But honestly, most projects don't need this complexity!
Installation Patterns
Two Commands You'll Use Most
That's it! Simple and effective.
Other Installation Options
Working on Locked-Down Platforms
When You Can't Install UV
Many enterprise/NHS environments don't allow installing new tools like UV. The good news? The organized dependency structure still helps with traditional pip!
With organized pyproject.toml, you can still benefit:
# Use pip with optional dependencies
pip install -e . # Core dependencies only
pip install -e .[dev] # Core + development tools
# Or export to requirements files for teams
uv export --group dev > requirements-dev.txt # (when UV is available)
# Then share requirements-dev.txt for pip users
pip install -r requirements-dev.txt
Key benefits even with just pip: - Clear separation of production vs development dependencies - Easy to share specific requirement sets with team members - Future-ready when you can eventually use modern tools like Poetry or Hatch - Better project organization and documentation
Do This
- Organize dependencies: Use
pyproject.toml
to separate production and development dependencies - Use the tools available: UV when possible, pip when necessary - both work with organized dependencies
- Pin appropriately: Use
>=
for minimum versions, avoid overly specific pins - Document your setup: Make it clear how team members should install dependencies
- Plan for constraints: Consider locked-down environments when choosing your approach
Avoid This
- All dependencies in one place: Don't mix production and development dependencies
- Unpinned dependencies: Specify minimum versions for stability
- Over-pinning: Avoid exact version pins unless absolutely necessary
- Assuming everyone can use modern tools: Not everyone can install UV on their systems
Troubleshooting
Common Issues
Version Conflicts
Checkpoint
Before moving to the next workshop, verify you can:
- Create and activate virtual environments with both
venv
anduv
- Understand the difference between direct and sub-dependencies
- Organize dependencies in
pyproject.toml
using dependency groups - Install dependencies with both
pip
anduv sync
- Add and remove packages using UV commands
- Understand the purpose of
uv.lock
files
Next Steps
Excellent work! You've successfully modernized your dependency management workflow while building on solid pip + venv
foundations.
Continue your learning journey - these workshops can be done in any order:
- Packaging with pyproject.toml - Make your code installable and reusable
- Documentation with MkDocs - Create professional documentation
- Pre-Commit Hooks - Automate code quality checks
- CI/CD with GitHub Actions - Automate testing and deployment
Additional Resources
RAP Community of Practice
- Why Use Virtual Environments - RAP guidance on virtual environments for reproducible analysis
UV (Modern Python Package Manager)
- UV Documentation - Complete UV guide and reference
- UV Working on Projects - Practical UV project workflows
- UV Migration Guide - Step-by-step pip to UV migration
- UV vs pip Comparison - Detailed comparison of tools
Python Project Configuration
- Writing pyproject.toml - Official guide to pyproject.toml
- PEP 621 - Project Metadata - Standard for pyproject.toml
- PEP 735 - Dependency Groups - Modern dependency organization
Traditional Python Packaging
- Python Packaging Guide - Official Python packaging documentation
- Virtual Environments Guide - Python.org official guide
- pip User Guide - Official pip documentation
Best Practices & Standards
- Python Packaging Best Practices - Official packaging guidelines
- Understanding Semantic Versioning - Version specification standards
- Python Enhancement Proposals (PEPs) - Python standards and proposals