Virtual Environments, Dependencies, pip, uv & Lockfiles
Managing dependencies in production Python systems requires strict environment isolation and deterministic build lockfiles. From standard venv mechanics to modern PEP 517/518 build standards (pyproject.toml) and high-speed Rust-based resolution engines (uv), understanding how dependencies are resolved, isolated, and installed is critical for reproducible CI/CD pipelines.
This chapter details Python virtual environment internals (pyvenv.cfg), pyproject.toml standards, deterministic lockfiles, and uv package management.
1. Virtual Environment Internals (pyvenv.cfg)
A Python virtual environment is not a container or virtual machine. It is a lightweight directory tree containing symlinks and a configuration file: pyvenv.cfg.
Virtual Environment Directory Layout:
.venv/
βββ pyvenv.cfg <-- Core configuration file
βββ bin/
β βββ python -> /usr/bin/python3.11 (Symlink to host interpreter)
β βββ pip
β βββ activate (Shell script modifying $PATH & $VIRTUAL_ENV)
βββ lib/python3.11/
βββ site-packages/ <-- Localized dependency installation directoryCPython Startup Resolution:
When the python binary inside .venv/bin starts up:
- CPython checks its executable directory for
pyvenv.cfg. - If found, it parses
home = <base_python_path>. - It sets
sys.prefixto.venv, while leavingsys.base_prefixpointing to the system Python installation. sys.pathis constructed usingsys.prefix/lib/pythonX.Y/site-packages, isolating third-party packages to the local environment.
2. Modern Packaging Standards (pyproject.toml)
Legacy Python packaging relied on imperative setup.py scripts that executed arbitrary Python code during installation. Modern packaging uses declarative standards:
- PEP 518 (
build-system): Defines the build backend required to package the project (e.g.hatchling,flit_core,setuptools). - PEP 621 (
[project]): Defines standard project metadata (dependencies, version, authors, scripts).
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-service"
version = "1.0.0"
dependencies = [
"fastapi>=0.100.0",
"pydantic>=2.0.0",
]3. High-Speed Dependency Resolution (uv vs pip)
Traditional pip resolves dependencies sequentially in Python, leading to multi-minute CI build times and complex backtracking resolution failures.
Modern Python engineering uses uv (a cargo-like Python package manager written in Rust):
Dependency Resolution Performance:
pip (Python): [ Downloads wheels ] -> [ Sequential Python resolver ] -> [ 45-120 seconds ]
uv (Rust): [ Parallel Rust fetch ] -> [ PubGrub SAT Resolver ] -> [ 0.5-2 seconds ]Why uv Wins:
- PubGrub Algorithm: Uses the PubGrub SAT solver for fast dependency resolution.
- Global Content-Addressable Cache: Hard-links wheels from a shared global cache into virtual environments, installing packages in milliseconds without duplicating disk usage.
4. Production Deterministic Lockfiles
Never deploy applications using loose requirements (fastapi>=0.100.0). Always generate a deterministic lockfile (uv.lock or requirements.txt generated via pip-compile) containing exact pinned versions and cryptographic SHA-256 hashes for every dependency and sub-dependency.