Python environments
An environment is a reproducibility boundary. The goal is that any machine, including CI and a teammate's laptop, resolves to the exact same package versions, isolated from the interpreter the OS ships.
Method
- Never install into system Python. The OS depends on its interpreter;
pip installinto it breaks tools and needs sudo. Create a per-project environment and treat the system Python as read-only. On managed hosts this is enforced by PEP 668 (externally-managed-environment); do not override it with--break-system-packages. - Choose the manager by constraint.
venvplus pip is built in and enough for simple projects.uvis the default for speed and a single tool that handles interpreters, venvs, resolution, and locking. Reserveconda/mambafor non-Python native dependencies (CUDA, MKL, GDAL, compilers) that PyPI wheels do not cover. - Separate declared from resolved. Declare direct dependencies with
loose bounds in
pyproject.toml. Resolve them once into a lockfile that pins every transitive package and its hash (uv lock,poetry.lock, orpip-compileproducingrequirements.txt). Commit the lockfile; humans edit declarations, tools own the lock. - Install from the lock, exactly. Development and CI run a synced,
hash-verified install (
uv sync --frozen,pip install --require-hashes -r requirements.txt) so a new release upstream cannot silently enter. Fail the build if the lock is out of date rather than re-resolving. - Pin the interpreter too. Record the Python version in
requires-pythonand a.python-version; a lockfile resolved on 3.12 can produce different packages on 3.11. Let the manager fetch the pinned interpreter rather than relying on whatever is on PATH. - Keep environments disposable. The environment is a build artifact, not a place to store state; never commit the venv directory. Rebuilding from the lockfile must be a routine, sub-minute operation, which is what makes a corrupted environment cheap to fix.
Boundaries
- Lockfiles pin PyPI packages, not system libraries. Reproducibility for native dependencies needs conda, a container image, or Nix.
- Platform-specific wheels mean a lock resolved on Linux may not install on macOS or Windows; generate or verify per-platform when you support many.
- This covers dependency reproducibility, not building your own distributable package; that is python-packaging.